From ff3e3a6f27686e546b765356f88142bb43ceacd0 Mon Sep 17 00:00:00 2001 From: Saket T Date: Sat, 24 Oct 2026 04:54:50 -0700 Subject: [PATCH 1/6] docs: distinguish development APIs and complete the first-test and vision guides --- README.md | 4 + SimLoop/docs/agents.md | 56 ++-- SimLoop/docs/assets/first-test-slide.csv | 151 +++++++++ SimLoop/docs/assets/first-test-slide.svg | 400 +++++++++++++++++++++++ SimLoop/docs/features.md | 2 +- SimLoop/docs/first-test.md | 27 +- SimLoop/docs/getting-started.md | 19 +- SimLoop/docs/index.md | 5 + SimLoop/docs/llms.md | 5 + SimLoop/docs/packages/loop.md | 7 +- SimLoop/docs/packages/sim.md | 24 +- SimLoop/docs/packages/vision.md | 58 +++- SimLoop/docs/troubleshooting.md | 14 +- docs/documentation-review-2026-10-24.md | 47 +++ simloop-agents/AGENTS.md | 11 +- simloop-agents/README.md | 87 ++--- simloop-starter/README.md | 9 +- tools/DOCS-IMAGES.md | 33 ++ tools/ExportSlide.java | 27 ++ tools/plot-first-test.py | 44 +++ 20 files changed, 880 insertions(+), 150 deletions(-) create mode 100644 SimLoop/docs/assets/first-test-slide.csv create mode 100644 SimLoop/docs/assets/first-test-slide.svg create mode 100644 docs/documentation-review-2026-10-24.md create mode 100644 tools/DOCS-IMAGES.md create mode 100644 tools/ExportSlide.java create mode 100644 tools/plot-first-test.py diff --git a/README.md b/README.md index 35844c0..ab9438e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ score automatically. Package root: `org.horizon36596.simloop`. Licence: BSD 3-Clause. +The source tree can be newer than the latest tag. Distortion, occlusion, tilted tags and synthetic +camera frames are development additions after `v0.2.0-alpha.1`; they are not in that pinned artifact. +Use the starter and documentation from the tag you install, or pin the exact source commit you test. + > **SimLoop is an alpha.** `v0.2.0-alpha.1` is the current alpha and the first public one. The API may > change between alpha versions, so pin an exact version in your build. It needs **FTC SDK 12.0**. > Support is best effort: a small FTC team maintains it in its own time. `v0.1.0-beta1`, the earlier diff --git a/SimLoop/docs/agents.md b/SimLoop/docs/agents.md index 4b838f0..e0e843e 100644 --- a/SimLoop/docs/agents.md +++ b/SimLoop/docs/agents.md @@ -7,42 +7,30 @@ and notes on spending less. It lives in [`simloop-agents/`](https://github.com/Horizon-36596/simloop/tree/main/simloop-agents) in the repository. Copy the folder into your FTC project. -!!! info "Optional, and inert" - The folder is Markdown and nothing else. No Gradle file reads it, nothing imports it, and no part of - SimLoop depends on it existing. If you want nothing to do with AI, delete the folder and nothing - changes. Everything else in this documentation is written for a human and stays that way. - -## Before the rest - -Three things, in short. The full version — which is longer, and is the canonical copy of this -argument — is in -[`simloop-agents/README.md`](https://github.com/Horizon-36596/simloop/blob/main/simloop-agents/README.md). -Read it before a team commits to this. - -- **It does not replace understanding your own code.** An agent will write a subsystem you do not - understand, and it will compile, and it will probably pass in the simulator. The architecture still - has to live in somebody's head, and an agent makes it easy to skip the part where it gets there. -- **It is not a way around learning to program.** Reviewing code you did not write and spotting the - confident wrong answer are real skills with their own discipline. This is a second thing to learn, - not a way to avoid the first. -- **It does genuinely lower the door.** A student who has never written Java can describe a mechanism in - plain English and get a working, tested simulation of it. That is the opportunity, and it is why the - folder exists. +The toolkit is optional Markdown. No build file reads it, and removing it does not affect the sim. + +## Before using it + +Someone on the team needs to understand and review the code the agent changes. Ask it to explain +the units, tick order, hardware names and assertions, then compare those with your robot. A passing +simulation checks the model you supplied; it does not validate the physical robot. + +The [toolkit README](https://github.com/Horizon-36596/simloop/blob/main/simloop-agents/README.md) +has the setup instructions and suggested workflow. ## Which tool -Any of them. [`AGENTS.md`](https://agents.md/) is an open format that Cursor, Codex, Gemini CLI, -Copilot's coding agent, Zed, Windsurf, Jules, Junie and Aider read from a repository root without being -configured to. The skills and prompts are plain Markdown and work anywhere you can paste text. +The instructions use the [`AGENTS.md`](https://agents.md/) format. Check how your coding tool loads +project instructions and confirm that it has read this file. The skills and prompts are plain +Markdown, so you can also provide them directly. -We use **Claude Code**, so the Claude-specific paths are the best-tested route. That is a statement of -what has actually been exercised, not a claim that the others are worse. +Horizon uses **Claude Code**; its paths are the ones the team has exercised most. ## What is in the folder | File | What it is | |---|---| -| `AGENTS.md` | The one that matters. What SimLoop is, the nine rules that stop an agent writing code that compiles and is wrong, the complete public API, and the shape of every test. Copy it to your repository root. | +| `AGENTS.md` | The library overview, API inventory, simulation rules and test structure. Copy it to your repository root and add your robot's details. | | `skills/` | Four procedures — write a sim test, add a mechanism, debug a failing sim, review sim code. Written for any agent; drop into `.claude/skills/` unmodified. | | `prompts/` | Copy-paste starting points, including one for learning and one for auditing your tests before a competition. | | `pointers/` | One-line files for the tools that need a nudge, and for a project that already has its own instructions file. They point at `AGENTS.md` rather than copying it. | @@ -58,16 +46,16 @@ produce a test that passes: - **The log-key prefix.** `Logger.recordOutput("Slide/x", v)` reads back as `"RealOutputs/Slide/x"`. - **Tick order.** `periodic()` then `advancePhysics()`. Reversed lags every response one tick and still passes. -- **The two frames.** A pose crossing between `plant` and `field` unrotated gives a confident wrong - answer rather than throwing. See [`field`](packages/field.md). -- **The wall clock.** Any real-time read destroys determinism, which is the whole point. +- **The two frames.** Convert a pose when it crosses between `plant` and `field`; the wrong frame + can produce incorrect geometry without throwing. See [`field`](packages/field.md). +- **The wall clock.** Robot decisions must use sim time so repeated runs can be compared. - **Assertions that cannot fail.** Asserting a slide stayed within the end stops the plant clamps to every tick tests the model, not the robot. -## The one habit worth keeping +## Check that the assertion can detect a failure **Make every test fail once, on purpose, before you trust it.** -An agent writes tests that pass — that is what it is optimising for. A test that passes because it -cannot fail looks exactly like a test that passes because the robot works, and the difference matters -most at the moment you are relying on it. Break the thing, watch it go red, put it back. +Temporarily break the behaviour the test is meant to check: remove the command, use an unreachable +target or shorten the run. Confirm that the relevant assertion fails, then restore the code. If the +test still passes, it is not checking the behaviour you intended. diff --git a/SimLoop/docs/assets/first-test-slide.csv b/SimLoop/docs/assets/first-test-slide.csv new file mode 100644 index 0000000..01d2b13 --- /dev/null +++ b/SimLoop/docs/assets/first-test-slide.csv @@ -0,0 +1,151 @@ +time_s,position_in,target_in +0.020000,0.047334210,12.0 +0.040000,0.179512718,12.0 +0.060000,0.383510375,12.0 +0.080000,0.648301629,12.0 +0.100000,0.964553551,12.0 +0.120000,1.324365988,12.0 +0.140000,1.721051606,12.0 +0.160000,2.148949697,12.0 +0.180000,2.603268577,12.0 +0.200000,3.079952170,12.0 +0.220000,3.575567086,12.0 +0.240000,4.087207020,12.0 +0.260000,4.612411838,12.0 +0.280000,5.149099084,12.0 +0.300000,5.695505995,12.0 +0.320000,6.250140424,12.0 +0.340000,6.811739298,12.0 +0.360000,7.379233446,12.0 +0.380000,7.951717837,12.0 +0.400000,8.528426376,12.0 +0.420000,9.102457419,12.0 +0.440000,9.661513879,12.0 +0.460000,10.194853193,12.0 +0.480000,10.693853930,12.0 +0.500000,11.151912437,12.0 +0.520000,11.564307726,12.0 +0.540000,11.928042241,12.0 +0.560000,12.241665599,12.0 +0.580000,12.505087786,12.0 +0.600000,12.719387620,12.0 +0.620000,12.886621554,12.0 +0.640000,13.009637179,12.0 +0.660000,13.091895055,12.0 +0.680000,13.137301776,12.0 +0.700000,13.150056536,12.0 +0.720000,13.134512783,12.0 +0.740000,13.095056013,12.0 +0.760000,13.035998225,12.0 +0.780000,12.961489084,12.0 +0.800000,12.875443466,12.0 +0.820000,12.781484727,12.0 +0.840000,12.682902751,12.0 +0.860000,12.582625657,12.0 +0.880000,12.483203856,12.0 +0.900000,12.386805079,12.0 +0.920000,12.295218922,12.0 +0.940000,12.209869456,12.0 +0.960000,12.131834456,12.0 +0.980000,12.061869866,12.0 +1.000000,12.000438199,12.0 +1.020000,11.947739647,12.0 +1.040000,11.903744806,12.0 +1.060000,11.868228031,12.0 +1.080000,11.840800558,12.0 +1.100000,11.820942680,12.0 +1.120000,11.808034341,12.0 +1.140000,11.801383706,12.0 +1.160000,11.800253300,12.0 +1.180000,11.803883494,12.0 +1.200000,11.811513144,12.0 +1.220000,11.822397338,12.0 +1.240000,11.835822237,12.0 +1.260000,11.851117092,12.0 +1.280000,11.867663559,12.0 +1.300000,11.884902473,12.0 +1.320000,11.902338312,12.0 +1.340000,11.919541538,12.0 +1.360000,11.936149108,12.0 +1.380000,11.951863362,12.0 +1.400000,11.966449577,12.0 +1.420000,11.979732413,12.0 +1.440000,11.991591499,12.0 +1.460000,12.001956379,12.0 +1.480000,12.010801037,12.0 +1.500000,12.018138163,12.0 +1.520000,12.024013359,12.0 +1.540000,12.028499399,12.0 +1.560000,12.031690691,12.0 +1.580000,12.033698020,12.0 +1.600000,12.034643669,12.0 +1.620000,12.034656963,12.0 +1.640000,12.033870294,12.0 +1.660000,12.032415630,12.0 +1.680000,12.030421542,12.0 +1.700000,12.028010721,12.0 +1.720000,12.025297994,12.0 +1.740000,12.022388798,12.0 +1.760000,12.019378087,12.0 +1.780000,12.016349639,12.0 +1.800000,12.013375717,12.0 +1.820000,12.010517043,12.0 +1.840000,12.007823044,12.0 +1.860000,12.005332318,12.0 +1.880000,12.003073292,12.0 +1.900000,12.001065008,12.0 +1.920000,11.999318027,12.0 +1.940000,11.997835388,12.0 +1.960000,11.996613610,12.0 +1.980000,11.995643701,12.0 +2.000000,11.994912150,12.0 +2.020000,11.994401877,12.0 +2.040000,11.994093141,12.0 +2.060000,11.993964367,12.0 +2.080000,11.993992908,12.0 +2.100000,11.994155716,12.0 +2.120000,11.994429933,12.0 +2.140000,11.994793389,12.0 +2.160000,11.995225013,12.0 +2.180000,11.995705163,12.0 +2.200000,11.996215877,12.0 +2.220000,11.996741044,12.0 +2.240000,11.997266513,12.0 +2.260000,11.997780142,12.0 +2.280000,11.998271787,12.0 +2.300000,11.998733256,12.0 +2.320000,11.999158217,12.0 +2.340000,11.999542080,12.0 +2.360000,11.999881855,12.0 +2.380000,12.000175993,12.0 +2.400000,12.000424215,12.0 +2.420000,12.000627340,12.0 +2.440000,12.000787109,12.0 +2.460000,12.000906014,12.0 +2.480000,12.000987133,12.0 +2.500000,12.001033975,12.0 +2.520000,12.001050340,12.0 +2.540000,12.001040189,12.0 +2.560000,12.001007530,12.0 +2.580000,12.000956317,12.0 +2.600000,12.000890372,12.0 +2.620000,12.000813309,12.0 +2.640000,12.000728485,12.0 +2.660000,12.000638958,12.0 +2.680000,12.000547460,12.0 +2.700000,12.000456377,12.0 +2.720000,12.000367748,12.0 +2.740000,12.000283264,12.0 +2.760000,12.000204282,12.0 +2.780000,12.000131837,12.0 +2.800000,12.000066666,12.0 +2.820000,12.000009236,12.0 +2.840000,11.999959767,12.0 +2.860000,11.999918264,12.0 +2.880000,11.999884551,12.0 +2.900000,11.999858295,12.0 +2.920000,11.999839038,12.0 +2.940000,11.999826229,12.0 +2.960000,11.999819245,12.0 +2.980000,11.999817416,12.0 +3.000000,11.999820053,12.0 diff --git a/SimLoop/docs/assets/first-test-slide.svg b/SimLoop/docs/assets/first-test-slide.svg new file mode 100644 index 0000000..0b4a05c --- /dev/null +++ b/SimLoop/docs/assets/first-test-slide.svg @@ -0,0 +1,400 @@ + + + + + + + + image/svg+xml + + + SimLoop documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.0 + + + + + + + + + + + + + 0.5 + + + + + + + + + + + + + 1.0 + + + + + + + + + + + + + 1.5 + + + + + + + + + + + + + 2.0 + + + + + + + + + + + + + 2.5 + + + + + + + + + + + + + 3.0 + + + + Simulated time (s) + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 2 + + + + + + + + + + + + + 4 + + + + + + + + + + + + + 6 + + + + + + + + + + + + + 8 + + + + + + + + + + + + + 10 + + + + + + + + + + + + + 12 + + + + + + + + + + + + + 14 + + + + Slide position (in) + + + + + + + + + + + + + + + + + + + Final: 12.000 in + + + First simulated test: slide position + + + + + + + Target: 12 in + + + + + + Recorded simulated position + + + + + + + + + + diff --git a/SimLoop/docs/features.md b/SimLoop/docs/features.md index d7f2aad..53ef7f5 100644 --- a/SimLoop/docs/features.md +++ b/SimLoop/docs/features.md @@ -1,6 +1,6 @@ # Every feature -Everything SimLoop `0.2.0-alpha.1` does, one line each. Each line links to the page that explains it. What +Features in the current source tree, one line each. Each line links to the page that explains it. What it does **not** do is on [What it does not do](limits.md). ## Run your code diff --git a/SimLoop/docs/first-test.md b/SimLoop/docs/first-test.md index 36517c7..75f6e44 100644 --- a/SimLoop/docs/first-test.md +++ b/SimLoop/docs/first-test.md @@ -3,11 +3,10 @@ By the end of this page you have a test that drives a linear slide to a target in simulation, asserts it got there, and leaves an RLOG you can open in AdvantageScope. -!!! success "This example is not a sketch" - The code below is a real test in SimLoop's own test sourceset - - `SimLoop/src/test/java/org/horizon36596/simloop/example/SlideReachesTargetExampleTest.java` - and it - runs in CI with everything else. If a signature on this page ever stops compiling, the build goes red - before you find out the hard way. +The maintained example is +`SimLoop/src/test/java/org/horizon36596/simloop/example/SlideReachesTargetExampleTest.java`. +It runs with the library's tests. Use the source at your dependency's tag if a snippet here differs +from the version you installed. ## The five things a simulated test always has @@ -161,7 +160,7 @@ class SlideReachesTargetExampleTest { } ``` -## Two things that will catch you +## Check the log key, motor feedback and tick order **The key you log is not the key you read.** PsiKit files everything `Logger.recordOutput` writes beneath `RealOutputs/`. A run that logged `"Slide/positionInches"` is queried as @@ -192,18 +191,26 @@ and run your module's test task: ./gradlew :TeamCode:testDebugUnitTest ``` -Three seconds of robot time takes well under a second of yours. +The scenario advances three seconds of simulated time. Build and test startup time depend on your +machine and whether Gradle has cached its dependencies. -(The copy that lives in SimLoop's own tests is not something you need; it is there so that this page -cannot drift away from the library.) +The complete example also lives in SimLoop's test sources. Compare with the version at your pinned +tag if you adapt the code. ## Opening the log -The run wrote `build/sim/slide-reaches-target.rlog`. Open it in +The test writes `build/sim/slide-reaches-target.rlog` relative to the test task's working directory. +For a standard FTC `TeamCode` test, look under `TeamCode/build/sim/`. Open the file in [AdvantageScope](https://github.com/Mechanical-Advantage/AdvantageScope): **File - Open**, pick the file, then drag `RealOutputs/Slide/positionInches` onto a line graph. You should see it rise and flatten at 12 inches. +![Recorded slide position rises, overshoots the 12 inch target, and settles back to it](assets/first-test-slide.svg) + +*A recorded run of this example: 150 ticks at 0.02 s, using the example's placeholder plant. The final +position is 12.000 in when rounded to three decimals. This is simulated data, not a robot measurement. +[Download the plotted values](assets/first-test-slide.csv).* + !!! warning "`build/` is deleted by `./gradlew clean`" The log is a build output, not a document. If it is gone, re-run the test - that is the command that regenerates it. diff --git a/SimLoop/docs/getting-started.md b/SimLoop/docs/getting-started.md index b3ae24c..cfb73e7 100644 --- a/SimLoop/docs/getting-started.md +++ b/SimLoop/docs/getting-started.md @@ -1,6 +1,7 @@ # Installing it -There are two ways in, and the first one is three steps. +Copy the starter folder to add SimLoop to an FTC project. The manual setup below explains the same +build settings if you want to maintain them yourself. !!! warning "Your project must be on FTC SDK 12.0" SimLoop `v0.2.0-alpha.1` is built against **FTC SDK 12.0.0**, and the earlier beta was built against @@ -30,7 +31,7 @@ your robot runs. Update the project first. ## The three-step way: copy the starter folder -[`simloop-starter/`](https://github.com/Horizon-36596/simloop/tree/main/simloop-starter) is one folder +[`simloop-starter/`](https://github.com/Horizon-36596/simloop/tree/v0.2.0-alpha.1/simloop-starter) is one folder that carries every build setting SimLoop needs, plus a small example robot that drives itself. 1. **Copy the folder** into the root of your FTC project, beside `TeamCode/` and `FtcRobotController/`. @@ -46,16 +47,20 @@ that carries every build setting SimLoop needs, plus a small example robot that ./gradlew :TeamCode:testDebugUnitTest ``` -Two tests run and pass in a few seconds, with no robot and no Driver Station. That is the example robot -driving itself in simulation. +The two starter tests should pass without a robot or Driver Station. On the first run, Gradle may +need to download dependencies or wait for JitPack to build the pinned tag. Later runs use the cache. + +Copy the starter from the same tag as the dependency it declares. The repository's `main` branch can +contain examples for APIs that have not been released. After the starter passes, add a test for your +own robot; passing the example checks the installation, not your robot's configuration or physics. !!! tip "Why a folder and not a quickstart repository" A team starts its season in one repository. A folder you drop into that repository needs no merge, and removing it is deleting the folder and one line. -The starter's own [README](https://github.com/Horizon-36596/simloop/tree/main/simloop-starter#readme) -says what is in it, and [Your first simulated test](first-test.md) walks through the same code line by -line. +The starter's own [README](https://github.com/Horizon-36596/simloop/tree/v0.2.0-alpha.1/simloop-starter#readme) +says what is in it. [Your first simulated test](first-test.md) walks through a separate slide example, +including the clock, plant, log and assertions. ## The manual way: what the starter folder is actually doing diff --git a/SimLoop/docs/index.md b/SimLoop/docs/index.md index 9f02bbd..a480bd7 100644 --- a/SimLoop/docs/index.md +++ b/SimLoop/docs/index.md @@ -22,6 +22,11 @@ No robot, no Driver Station, no emulator. A test run is a few seconds. [Every feature](features.md) lists what it does, one line each. +!!! note "Match the docs to your dependency" + These source docs can include changes newer than the published tag. The vision guide marks its + development APIs. For a build pinned to `v0.2.0-alpha.1`, use the + [docs at that tag](https://github.com/Horizon-36596/simloop/tree/v0.2.0-alpha.1/SimLoop/docs). + ## The fastest way in Copy one folder into your FTC project and add one line to `TeamCode/build.gradle`: diff --git a/SimLoop/docs/llms.md b/SimLoop/docs/llms.md index e49af36..994d710 100644 --- a/SimLoop/docs/llms.md +++ b/SimLoop/docs/llms.md @@ -8,6 +8,11 @@ the right page instead of crawling navigation. ## Read this first +Check the dependency's exact tag or source commit before using this index. It describes the current +source tree, which includes APIs added after `v0.2.0-alpha.1`. In particular, distortion, occluders, +tilted tags and synthetic frames are development APIs. Read the matching source or generated API +reference before calling them; the published alpha coordinate above does not contain them. + - **[Every feature](features.md)**: one line per feature, with a link to its page. - **[What it does not do](limits.md)**: the authoritative list of what is out of scope. If a task needs something on that list, there is no API for it; say so rather than searching. A photoreal camera image, diff --git a/SimLoop/docs/packages/loop.md b/SimLoop/docs/packages/loop.md index 1088828..38be9ec 100644 --- a/SimLoop/docs/packages/loop.md +++ b/SimLoop/docs/packages/loop.md @@ -32,7 +32,7 @@ run.maxValue("RealOutputs/Slide/positionInches"); // worst case over the run **The key needs its `RealOutputs/` prefix.** Ask for a key nothing logged and you get a `MetricNotFoundException`, never a zero — a silent zero is a passing test that measured nothing. -## `UNKNOWN` is a real answer, and it is the important one +## Read unknown guardrails before accepting a score A guardrail that cannot check itself from a run returns `UNKNOWN` with a reason, rather than passing. An envelope with four unknowns in it is guarding much less than it appears to, so read @@ -42,9 +42,8 @@ because no scenario logs a loop time yet. ## It will not report a score that did not reproduce `StopReport.reportedScore()` throws if the best candidate failed to reproduce, rather than handing back a -best-effort number. The one mistake the class exists to prevent is printing a score the second run did not -agree with. The number that failed is still available, named `unreproducedScoreForDiagnosis()`, so a -determinism failure can say *which* number did not come back. +best-effort number. The unreproduced value is available through `unreproducedScoreForDiagnosis()` so +you can investigate the difference without reporting it as a verified result. ## Units and frames diff --git a/SimLoop/docs/packages/sim.md b/SimLoop/docs/packages/sim.md index f7a2801..da94f1d 100644 --- a/SimLoop/docs/packages/sim.md +++ b/SimLoop/docs/packages/sim.md @@ -36,7 +36,17 @@ ScenarioRunner.run( The timer must be constructed, and anything sim-side that reads time must be wired to it, **before** this call — that wiring has to happen before the robot under test is built. -## It refuses to hand you a log you cannot trust +### Logging ownership + +`ScenarioRunner` starts and stops PsiKit for the run. Log values inside the tick body; do not call +`Logger.start()` or `Logger.end()` there. Use `runUntil`'s extra-receivers argument when a live session +needs another output. + +PsiKit's logger is static. Run scenarios sequentially within one JVM, and give separate test processes +different log paths. If another test creates its own PsiKit logging session, initialise that session +explicitly rather than relying on state left by an earlier test. + +## Log completeness checks `ScenarioRunner.run` throws rather than returning quietly when any of four things is true: PsiKit's async writer queue overflowed, the file is missing or empty, it decodes to a different number of frames than @@ -57,8 +67,9 @@ Field keys gain a prefix. `Logger.recordOutput("Slide/positionInches", x)` is re `RealOutputs/Slide/positionInches`, both by `RlogDecodedCompare` and by [`RunResult`](loop.md). -Two runs of the same scenario must decode identically, field for field. That is what -`RlogDecodedCompare` is for, and it is how determinism is tested rather than asserted. +Use `RlogDecodedCompare` to compare repeated runs field by field. It ignores PsiKit's console and +writer queue-depth metadata, which reflect writer scheduling. Differences in robot-authored fields +remain failures. ## What this package will not do @@ -68,6 +79,7 @@ Two runs of the same scenario must decode identically, field for field. That is - **It will not schedule commands.** If you use SolversLib, SolversLib still owns control flow. - **It will not vary the time step.** The tick is fixed, on purpose — a variable step is the single easiest way to make two runs disagree. -- **It will not read wall-clock time anywhere.** If you introduce a `System.nanoTime()` on a sim path, you - have broken determinism, and nothing here will catch it for you. The one class that may wait on the - wall clock is `sim.live.RealTimePacer`, and it never changes what a tick computes. +- **It will not make wall-clock-dependent robot behaviour deterministic.** Model time comes from + `FakeTimer`. The runner may wait for log I/O, and `sim.live.RealTimePacer` can pace live input, but + neither advances model time. In your scenario code, use the fake clock or the + [OpMode runner's supported timer rewriting](opmode.md). diff --git a/SimLoop/docs/packages/vision.md b/SimLoop/docs/packages/vision.md index d2c3701..f5e3236 100644 --- a/SimLoop/docs/packages/vision.md +++ b/SimLoop/docs/packages/vision.md @@ -2,6 +2,12 @@ `org.horizon36596.simloop.vision` +!!! note "Development APIs" + Distortion, `CameraOccluders`, tilted tags and synthetic frames were added after the + `v0.2.0-alpha.1` tag. The pinned alpha does not contain them. Use documentation from the tag you + install; for development work, pin the exact source commit you tested. See + [Installing it](../getting-started.md). + A simulated camera on the robot, at two levels: - **Geometry.** Which game pieces and AprilTags fall inside the image and where, as numbers. Through a @@ -33,6 +39,17 @@ A simulated camera on the robot, at two levels: `FakeLimelight3A` in [`fakehardware`](fakehardware.md) is built on `SimTagCamera`, so a Limelight and a plain camera share one description. +### Choose the output your test needs + +| What you are testing | Use | What the test receives | +|---|---|---| +| Projection, tracking or tag transforms | `SimCamera` or `SimTagCamera` | Sightings and geometry, without rendering pixels | +| Code that calls the SDK's Limelight API | `FakeLimelight3A` | SDK result objects, with configured latency, noise and dropouts | +| A colour or contour processor | `SimFrameRenderer` | RGB or RGBA bytes and per-pixel ground truth | + +Use the geometry APIs when pixels are not part of the code under test. Synthetic tag patterns are +not decodable AprilTags; use tag sightings or the Limelight fake for tag-result handling. + ## How it is used From `src/test/java/org/horizon36596/simloop/vision/SimCameraTest.java`: a 70 degree, 640 x 400 camera, @@ -52,12 +69,18 @@ List seen = camera.observe(tracker.getPieces(), 0.0, 0.0, 0.0); camera.record("Vision", tracker.getPieces()); // logs the camera pose and the sightings ``` -To check a pipeline's floor projection, put a sighting's centre pixel back on the floor and compare with -the piece's true position: +To check a pipeline's projection, intersect the sighting's centre ray with the plane through the +piece's centre. The last argument is that plane's height above the floor in inches; for a ball resting +on the floor, it is the ball's radius. A frame can contain no sightings, so check the list first: ```java -CameraSighting s = seen.get(0); -RobotFramePoint p = camera.groundPointInRobotFrame(s.getCentreXPx(), s.getCentreYPx(), 1.4); // ball radius, in +if (!seen.isEmpty()) { + CameraSighting s = seen.get(0); + RobotFramePoint p = camera.groundPointInRobotFrame( + s.getCentreXPx(), s.getCentreYPx(), 1.4); // centre-plane height, inches + // p can be null if the ray does not reach the plane. + // Otherwise compare it with the true position in the ROBOT frame, not the field frame. +} ``` ### A distorted lens @@ -110,6 +133,10 @@ List notBehindTheArm = tags.observe(layout, robotX, robotY, robotHe ### Synthetic frames for a pixel pipeline +This fragment assumes you already have a tracker, robot pose, blockers, tag layout and processor. +Your test must provide OpenCV and load its native library before creating a `Mat`. SimLoop does not +install OpenCV or create a `VisionPortal`. + ```java SimFrameRenderer renderer = SimFrameRenderer.builder(lens, front) .style(FrameStyle.builder() @@ -123,12 +150,29 @@ SimFrameRenderer renderer = SimFrameRenderer.builder(lens, front) SimFrame frame = renderer.render(tracker.getPieces(), x, y, heading, blockers, layout); // Your real processor, unchanged. SimLoop has no OpenCV dependency; your test has it. +// Initialise once per processor, outside the frame loop. null is suitable only if your processor +// does not use CameraCalibration; otherwise supply the calibration it expects. +processor.init(frame.getWidthPx(), frame.getHeightPx(), null); + +// For each frame; captureTimeNanos comes from your simulated capture clock, in nanoseconds. Mat rgb = new Mat(frame.getHeightPx(), frame.getWidthPx(), CvType.CV_8UC3); -rgb.put(0, 0, frame.getRgbBytes()); // getRgbaBytes() for CV_8UC4 -processor.init(frame.getWidthPx(), frame.getHeightPx(), null); // once -processor.processFrame(rgb, captureTimeNanos); +try { + rgb.put(0, 0, frame.getRgbBytes()); + processor.processFrame(rgb, captureTimeNanos); + // Read and assert the processor's detections here, before releasing the input. +} finally { + rgb.release(); +} ``` +The bytes are **RGB**, not BGR. Use `getRgbaBytes()` with `CV_8UC4` for a processor that expects RGBA, +or convert explicitly for one that expects BGR. Match the channel order used by your robot's pipeline. +Call `init` once, then `processFrame` for each capture; reinitialising per frame can erase tracking state. + +Seeded noise also depends on the renderer's frame counter. To repeat a sequence byte for byte, create +a fresh renderer with the same seed and render the same scenes in the same order. Two consecutive +frames from one noisy renderer need not have identical pixels. + `SimFrame.getPieceIdAt(x, y)`, `pixelCountOfPiece(id)` and `centroidOfPiece(id)` are ground truth for checking what the detector found. `writePng(out)` saves a frame; the PNG carries a comment saying it is synthetic. Rendering costs tens of milliseconds a frame at 640 x 480, so keep it to the tests that need diff --git a/SimLoop/docs/troubleshooting.md b/SimLoop/docs/troubleshooting.md index 93c289e..a3f2d30 100644 --- a/SimLoop/docs/troubleshooting.md +++ b/SimLoop/docs/troubleshooting.md @@ -1,7 +1,7 @@ # Troubleshooting -Every failure on this page has been seen for real. They are here because each one *reads* like a bug in -your robot code and is not. +Start with the error message or symptom below. Check the build setup, clock and log path before +changing your robot code. ## `Could not find org.psilynx.psikit:core` @@ -50,12 +50,14 @@ Check the order inside your scenario body. It must be: read state, decide, comma ## Two runs of the same scenario produce different logs -Something on a sim path is reading a wall clock. `System.nanoTime()`, `System.currentTimeMillis()`, and -anything built on them — `ElapsedTime` is the usual one, because it is `System.nanoTime()` underneath and -looks innocent. +First check for wall-clock reads: `System.nanoTime()`, `System.currentTimeMillis()`, and timers built +on them, including the SDK's `ElapsedTime`. Also check unseeded randomness, iteration order, shared +static state, and concurrent tests writing to the same log path. Everything sim-side must take its time from the `FakeTimer` you constructed. `RlogDecodedCompare` is what -tells you the two runs differ and in which field. +tells you the two runs differ and in which field. Use the +[OpMode runner](packages/opmode.md) for supported timer rewriting in unchanged team classes, or +[inject sim time](sim-safe-timers.md) into code you control. ## The run threw, and the message is about the RLOG rather than my robot diff --git a/docs/documentation-review-2026-10-24.md b/docs/documentation-review-2026-10-24.md new file mode 100644 index 0000000..b9904db --- /dev/null +++ b/docs/documentation-review-2026-10-24.md @@ -0,0 +1,47 @@ +# Documentation review, 2026-10-24 + +## Source checked + +GitHub was checked before editing. `main` was `7e06448`; the queued work was vision #24, streaming +RLOG reads #21, scenario cleanup #22, docs analytics #23 and branding #25. The integration comment on +#24 records a combined local run at `eb91fad`. It does not confirm a publication time. + +This documentation branch is based on `feat/vision-fidelity` at `4b40ef9`. It supplements the vision +documentation already in #24. The `v0.2.0-alpha.1` tag resolves to `9116406` and does not contain the +new distortion, occlusion, tilted-tag or synthetic-frame classes. Installation docs must not imply +that those classes resolve from the pinned alpha coordinate. + +## Changes made + +- Marked the source/tag boundary on the home page, feature list, vision page, agent index and README. +- Linked installation to the starter at the pinned tag and explained first-run dependency downloads. +- Clarified that the installed starter tests and the slide tutorial are different examples. +- Added vision output selection, channel order, native OpenCV setup, processor initialization, + calibration requirements, Mat cleanup and deterministic frame-sequence guidance. +- Explained projection height, robot/field coordinates, empty sightings and a missing plane intersection. +- Added logging ownership and corrected the distinction between model time and I/O waits. +- Expanded nondeterminism troubleshooting beyond wall-clock reads. +- Replaced broad claims about automatic tool support and repeated agent advice with explicit setup and + review steps. Simplified the toolkit README without changing its workflow. +- Added a plot of a real run of the documented slide test, its exported values and regeneration tools. + +## Verification + +- `:SimLoop:docsSite --offline` passes, including strict MkDocs, version check and API doclint. +- The documented slide example passes: one test, no failures, errors or skips. +- Its RLOG was decoded with `RlogTimeSeries`: 150 frames, final position 11.999820053 in, peak + 13.150056536 in. These pass the tutorial's 0.25 in final tolerance and 14 in peak bound. +- The tutorial's executable Java matches the maintained test source; the only omitted block is the + class-level documentation comment. +- Local file and anchor crawl passed for all 29 rendered guide pages before the final additions; + the final pass is recorded in the PR. Generated Javadoc is checked by `docsSite`. + +The plot describes simulated values from the example's placeholder model, not measured robot motion. +The OpenCV fragment requires the consuming team's processor and native library; it is not a standalone +test, and no end-to-end OpenCV execution is claimed for this patch. + +## Pending work already covered elsewhere + +#21 adds the long-log streaming documentation. #22 owns the logger-reset fix. #23 owns the counter and +privacy page; #25 owns the logo assets. Keep their documentation with their code when merging. A +published tag containing #24 must be chosen separately; this patch changes no version or release. diff --git a/simloop-agents/AGENTS.md b/simloop-agents/AGENTS.md index 6499efb..5268a49 100644 --- a/simloop-agents/AGENTS.md +++ b/simloop-agents/AGENTS.md @@ -1,9 +1,8 @@ # AGENTS.md — this project uses SimLoop -Instructions for an AI coding agent working in this repository. Written in the open `AGENTS.md` format, -which Cursor, Codex, Gemini CLI, Copilot's coding agent, Zed, Windsurf, Jules, Junie and Aider read from -the repository root without being told to. Claude Code reads it too when the project has no -`CLAUDE.md`; if yours does, one import line in it points here — see `simloop-agents/pointers/`. +Instructions for an AI coding agent working in this repository, in the `AGENTS.md` format. +Check how your tool loads project instructions and confirm that this file is included. The +`simloop-agents/pointers/` folder has files for tools and projects that need an explicit pointer. Everything above the last heading is about **SimLoop**, the simulation library this project tests with, and is the same in every project. The last section is about your robot and is yours to fill in. @@ -136,7 +135,9 @@ a specific subsystem, the answer is a config implementation on this side, not a ## The whole public surface -Thirteen packages. Complete as of `v0.2.0-alpha.1`. Every feature, one line each: +Thirteen packages in the current source tree. Check this project's pinned dependency before using a +type below: distortion, occluders, tilted tags and synthetic frames were added after +`v0.2.0-alpha.1` and are absent from that tag. Every feature, one line each: . | Package | Classes | What it is for | diff --git a/simloop-agents/README.md b/simloop-agents/README.md index 3ad9ccb..8ceefc2 100644 --- a/simloop-agents/README.md +++ b/simloop-agents/README.md @@ -4,72 +4,25 @@ A toolkit for teams who want to use an AI coding agent on their robot code: a co any agent how SimLoop works, four skills for the jobs that come up, prompts to start from, and notes on not spending more than you need to. -**Optional. Entirely.** This folder is Markdown and nothing else. No Gradle file reads it, no test -imports it, no part of SimLoop depends on it existing. A team that wants nothing to do with AI can -delete this folder and notice no difference at all — and that is the point, not a disclaimer. +The toolkit is optional Markdown. No build file reads it, and removing it does not affect SimLoop. ---- +## Review what the agent changes -## Read this before the rest +Someone on the team needs to understand the robot code and be able to debug it without the agent. +Ask for an explanation of the hardware names, units, tick order and assertions before accepting a +change. Compare the model's constants with your measurements. A passing simulation checks behaviour +against that model; it does not prove that the real robot will behave the same way. -### It does not replace understanding your own code - -An agent will happily write a subsystem you do not understand. It will compile. It will probably even -work in the simulator. And then something goes wrong at a competition, at 9:40am, with no Wi-Fi, and the -only thing that gets the robot back on the field is somebody on the team who knows how the code is put -together. - -That person cannot be conjured on the morning. **The architecture of your robot code has to live in -somebody's head**, and an agent is very good at letting you skip the part where it gets there. That is -the real risk, and it is worth naming plainly rather than burying in a footnote. - -Everything in this folder is built to push the other way. The skills make the agent explain what it did -and why. The prompts ask it to show you a test failing before you trust it. There is a prompt whose -whole job is to make it teach you and then check whether you followed. Use them. - -A fair test of whether you are using this well: **could you explain, to another team, why your code is -shaped the way it is?** If not, you have output but you do not have understanding, and the agent has -been doing your learning for you. - -### It is not a way around learning to program - -*FIRST* exists to teach people things, and a team that ships a robot nobody understands has won -nothing worth having. - -But "learn to program by hand" and "learn to program with an agent" are not the same skill and neither -one is fake. Reviewing code you did not write, spotting the confident wrong answer, knowing when the -tool is out of its depth, writing a specification precise enough to be built from — those are real -skills, they are hard, and they are the ones a student will actually use in five years. This folder -treats working with an agent as **a second thing to learn**, with its own discipline, not as a way to -avoid the first. - -If your team is learning to program by hand, keep doing that. This is for the teams who want the other -path available too, or who want both. - -### It does genuinely lower the door - -Worth saying, because the two paragraphs above are cautious and this part is the actual opportunity: a -student who has never written Java can now describe a mechanism in plain English and get a working, -tested simulation of it. A team with one programmer and six builders can have seven people contributing -to the code. Someone who would have bounced off a semicolon in week one can be useful in week one, and -can learn the language afterwards because they now have a reason to. - -That is a real gain, and it is why this folder exists. - ---- +Use the learning prompt when a student needs an explanation. Use the review procedure to check a +change, and make the relevant assertion fail once before trusting a new test. ## Which tool -**Any of them.** `AGENTS.md` is an open format that Cursor, Codex, Gemini CLI, Copilot's coding agent, -Zed, Windsurf, Jules, Junie and Aider read from your repository root without being configured to. The -skills and prompts are plain Markdown and work anywhere you can paste text. - -If you want a recommendation: we use **Claude Code**, and this folder was built while using it, so the -Claude-specific paths (`.claude/skills/`, `CLAUDE.md`) are the best-tested route. That is a preference -and a plain statement of what we have actually exercised — not a claim that the others are worse. Use -what your team has access to. Nothing here locks you in, and moving between tools costs you a file copy. +The instructions use the `AGENTS.md` format. Confirm that your coding tool has loaded them; tools +can differ in how they find project instructions. You can also ask it to read the file directly. +Skills and prompts are plain Markdown and can be supplied in the same way. ---- +Horizon uses Claude Code and has exercised its paths most. The setup below includes those paths. ## Setting it up @@ -98,10 +51,9 @@ knows your robot writes noticeably better code than one that has to guess. The * agents* prompt in [`prompts/`](prompts/README.md) will do the first draft by reading your code, and you correct it. -**For most tools, that is the whole setup.** Cursor, Codex, Gemini CLI, Zed, Windsurf, Jules, Junie, -Aider and Copilot's coding agent read a root `AGENTS.md` on their own, so you are done — go and use the -[prompts](prompts/README.md). The two steps below exist because Claude Code needs a little more wiring, -not because it gets more attention. +After copying the file, confirm that your tool has loaded it. If it needs a different instructions +file, use the appropriate pointer from [`pointers/`](pointers/README.md), or ask it to read +`AGENTS.md` explicitly. **3. Claude Code only: copy [`skills/`](skills) into `.claude/skills/`** in your project root: @@ -119,16 +71,15 @@ who clones it gets them. or tell the agent to read `simloop-agents/skills//SKILL.md`. Nothing in them is Claude-specific except the folder they can be dropped into. -**4. Claude Code only: if your project already has a `CLAUDE.md`**, add one import line to it. See -[`pointers/CLAUDE.md`](pointers/CLAUDE.md) for why: Claude Code reads a root `AGENTS.md` on its own, but -only when no `CLAUDE.md` exists, so a project with both and no import silently loses everything in -`AGENTS.md`. +**4. If your project has a `CLAUDE.md`**, add an explicit pointer to `AGENTS.md` there. +[`pointers/CLAUDE.md`](pointers/CLAUDE.md) contains the import line. Confirm in the session that the +agent has loaded both the SimLoop guidance and your robot-specific instructions. ## What is in here | File | What it is | |---|---| -| [`AGENTS.md`](AGENTS.md) | The one that matters. What SimLoop is, the nine rules that stop an agent writing code that compiles and is wrong, the complete public API, and the shape of every test. Copy it to your repository root. | +| [`AGENTS.md`](AGENTS.md) | The library overview, API inventory, simulation rules and test structure. Copy it to your repository root and add your robot's details. | | [`skills/`](skills) | Four procedures: write a sim test, add a mechanism, debug a failing sim, review sim code. Written for any agent; drop straight into `.claude/skills/` unmodified. | | [`prompts/`](prompts/README.md) | Copy-paste starting points, including one for learning and one for auditing your tests before a competition. | | [`pointers/`](pointers) | One-line files for the tools that need a nudge, and for a project that already has its own instructions file. Deliberately thin — they point at the one copy rather than duplicating it. | diff --git a/simloop-starter/README.md b/simloop-starter/README.md index 3de15ae..7e3d5e9 100644 --- a/simloop-starter/README.md +++ b/simloop-starter/README.md @@ -4,6 +4,10 @@ Copy **this folder** into the root of your FTC project, add **one line** to `Tea run the tests. That is the whole installation. You do not clone the SimLoop repository, you do not build it, and you do not copy any of its source into your project. +Copy this folder from the same tag as `simloopVersion` below. The +[starter at `v0.2.0-alpha.1`](https://github.com/Horizon-36596/simloop/tree/v0.2.0-alpha.1/simloop-starter) +matches the pinned alpha. Examples on `main` may use development APIs absent from that artifact. + > ## Your FTC project must be on SDK 12.0 > > SimLoop `v0.2.0-alpha.1` is built against **FTC SDK 12.0.0** (the earlier beta used 11.2.1). Update @@ -46,8 +50,9 @@ apply from: "$rootDir/simloop-starter/simloop.gradle" ./gradlew :TeamCode:testDebugUnitTest ``` -Two tests run and pass, in a few seconds, with no robot and no Driver Station. They are the example robot -in this folder driving itself in simulation. +The two example tests should pass without a robot or Driver Station. The first run may take longer +while Gradle downloads dependencies. These tests check the installation using the example robot; +add tests for your own hardware names, mechanisms and OpModes next. ## Why one line and not ten diff --git a/tools/DOCS-IMAGES.md b/tools/DOCS-IMAGES.md new file mode 100644 index 0000000..24fa4de --- /dev/null +++ b/tools/DOCS-IMAGES.md @@ -0,0 +1,33 @@ +# Documentation plot sources + +`SimLoop/docs/assets/first-test-slide.svg` is a plot of an actual example test run, not a drawing of an +expected trajectory. Its numeric source is `first-test-slide.csv` beside it. The data was recorded on +2026-10-24 from `SlideReachesTargetExampleTest` at source commit `4b40ef9`, using its placeholder plant +parameters, 150 ticks at 0.02 s and a 12 in target. It is simulated data, not a robot measurement. + +To refresh it, first run the example from the repository root: + +```powershell +./gradlew :SimLoop:testDebugUnitTest --tests org.horizon36596.simloop.example.SlideReachesTargetExampleTest +``` + +`ExportSlide.java` uses SimLoop's own `RlogTimeSeries` reader to export the recorded position. Run it +with a JDK that supports source-file execution. Its classpath needs the compiled SimLoop classes and +the pinned PsiKit core jar. On Windows, for the current Android Gradle Plugin layout: + +```powershell +$docsClasses = (Resolve-Path 'SimLoop/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes').Path +# Set this to the resolved org.psilynx.psikit:core:0.2.0-beta2 jar in your Gradle cache. +$docsPsiKitJar = '' +java --class-path "$docsClasses;$docsPsiKitJar" tools/ExportSlide.java ` + SimLoop/build/sim/slide-reaches-target.rlog SimLoop/docs/assets/first-test-slide.csv +python tools/plot-first-test.py +``` + +The plotting script needs Matplotlib in the Python environment used to run it. It is an optional +authoring tool; the documentation build uses the committed SVG and does not run Python plotting or +change the library's dependencies. On other systems use `:` between Java classpath entries. + +After regeneration, verify that there are 150 rows, that the final value is within 0.25 in of 12 in, +and that the peak is at most 14 in, matching the example's assertions. Update the source commit above +if the example changed, and inspect the plot before committing it. diff --git a/tools/ExportSlide.java b/tools/ExportSlide.java new file mode 100644 index 0000000..af20ab6 --- /dev/null +++ b/tools/ExportSlide.java @@ -0,0 +1,27 @@ +import java.io.PrintWriter; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Locale; + +import org.horizon36596.simloop.fit.RlogTimeSeries; + +/** Exports the documented slide example's RLOG to the CSV used by its plot. */ +class ExportSlide { + public static void main(String[] args) throws Exception { + if (args.length != 2) { + throw new IllegalArgumentException("Usage: ExportSlide.java "); + } + String key = "RealOutputs/Slide/positionInches"; + RlogTimeSeries series = RlogTimeSeries.read(Paths.get(args[0]), Collections.singletonList(key)); + double[] time = series.timeSeconds(); + double[] position = series.column(key); + try (PrintWriter out = new PrintWriter(args[1], "UTF-8")) { + out.println("time_s,position_in,target_in"); + for (int i = 0; i < time.length; i++) { + out.printf(Locale.ROOT, "%.6f,%.9f,12.0%n", time[i], position[i]); + } + } + System.out.println("Exported " + series.size() + " frames; final position " + + position[position.length - 1] + " in."); + } +} diff --git a/tools/plot-first-test.py b/tools/plot-first-test.py new file mode 100644 index 0000000..262a6d4 --- /dev/null +++ b/tools/plot-first-test.py @@ -0,0 +1,44 @@ +"""Draw the first-test guide's plot from exported RLOG values. Requires matplotlib.""" + +from pathlib import Path +import csv + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt + + +root = Path(__file__).resolve().parents[1] +assets = root / "SimLoop" / "docs" / "assets" +with (assets / "first-test-slide.csv").open(newline="", encoding="utf-8") as source: + rows = list(csv.DictReader(source)) +time = [float(row["time_s"]) for row in rows] +position = [float(row["position_in"]) for row in rows] +target = float(rows[0]["target_in"]) + +plt.rcParams.update({"font.size": 11, "svg.fonttype": "none", "svg.hashsalt": "simloop-first-test"}) +fig, ax = plt.subplots(figsize=(9.6, 4.3), layout="constrained") +fig.set_facecolor("#17061d") +ax.set_facecolor("#17061d") +ax.axhline(target, color="#ffcb5c", linestyle=(0, (5, 4)), linewidth=1.4, label="Target: 12 in") +ax.plot(time, position, color="#f86a43", linewidth=2.4, label="Recorded simulated position") +ax.set(xlim=(0, 3), ylim=(0, 14), xlabel="Simulated time (s)", ylabel="Slide position (in)") +ax.set_title("First simulated test: slide position", loc="left", color="#f4edf5", pad=16) +ax.tick_params(colors="#cbbdce") +ax.xaxis.label.set_color("#cbbdce") +ax.yaxis.label.set_color("#cbbdce") +ax.spines[["top", "right"]].set_visible(False) +for side in ("bottom", "left"): + ax.spines[side].set_color("#746079") +ax.grid(color="#6d5176", alpha=0.28, linewidth=0.7) +ax.legend(loc="lower right", frameon=False, labelcolor="#e8dfea", fontsize=10) +ax.annotate( + f"Final: {position[-1]:.3f} in", + xy=(time[-1], position[-1]), + xytext=(2.05, 9.0), + color="#e8dfea", + arrowprops={"arrowstyle": "-", "color": "#ac9ab1"}, +) +fig.savefig(assets / "first-test-slide.svg", metadata={"Date": None, "Creator": "SimLoop documentation"}) +print(f"Plotted {len(rows)} recorded frames; peak {max(position):.6f} in; final {position[-1]:.6f} in.") From f4de1fd0de96c53a1f547bc76fd84fc40e189954 Mon Sep 17 00:00:00 2001 From: Saket T Date: Sat, 24 Oct 2026 05:28:12 -0700 Subject: [PATCH 2/6] docs: fix API landing route and verify tutorial source --- README.md | 42 +++----- SimLoop/PUBLISHING.md | 22 ++-- SimLoop/build.gradle | 9 +- SimLoop/docs-tools/check-examples.py | 36 +++++++ SimLoop/docs/agents.md | 2 +- SimLoop/docs/api-reference.md | 29 +++++ SimLoop/docs/first-test.md | 3 + SimLoop/docs/getting-started.md | 7 +- SimLoop/docs/index.md | 9 +- SimLoop/docs/javadoc.md | 30 ------ SimLoop/docs/packages/config.md | 4 +- SimLoop/docs/packages/fit.md | 4 + SimLoop/docs/packages/live.md | 5 +- SimLoop/docs/packages/simclock.md | 8 +- SimLoop/docs/packages/vision.md | 4 + SimLoop/docs/sim-safe-timers.md | 2 +- SimLoop/docs/troubleshooting.md | 6 +- SimLoop/docs/unchanged-team-code.md | 11 +- SimLoop/mkdocs.yml | 2 +- docs/documentation-review-2026-10-24.md | 25 ++++- simloop-agents/README.md | 2 +- simloop-agents/pointers/CLAUDE.md | 10 +- simloop-agents/pointers/README.md | 18 ++++ .../pointers/copilot-instructions.md | 13 +-- simloop-agents/prompts/README.md | 4 +- simloop-agents/token-savers.md | 101 ++++++------------ 26 files changed, 223 insertions(+), 185 deletions(-) create mode 100644 SimLoop/docs-tools/check-examples.py create mode 100644 SimLoop/docs/api-reference.md delete mode 100644 SimLoop/docs/javadoc.md create mode 100644 simloop-agents/pointers/README.md diff --git a/README.md b/README.md index ab9438e..71d6a69 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,11 @@ the starter folder is what declares it. | Contribute a fix or a feature | [`CONTRIBUTING.md`](CONTRIBUTING.md) | | Change code in this repository | [`SimLoop/CLAUDE.md`](SimLoop/CLAUDE.md) | -`SimLoop/docs/` is written to be read as Markdown in the repository and also builds into a site. That -site is live at ****, with the generated API reference -under [`/javadoc/`](https://libraries.horizon36596.org/simloop/javadoc/). The -`horizon-36596.github.io/simloop/` address still answers and redirects there. How the hosting is put -together is in [`SimLoop/PUBLISHING.md`](SimLoop/PUBLISHING.md). +`SimLoop/docs/` can be read as Markdown and built into a site. Its publishing address is +****, with generated Javadoc under +[`/javadoc/`](https://libraries.horizon36596.org/simloop/javadoc/). If the site is unavailable, read the +docs at your pinned tag or build them locally with `./gradlew :SimLoop:docsSite`. +[`SimLoop/PUBLISHING.md`](SimLoop/PUBLISHING.md) explains the hosting and deployment checks. That path is this repository's name, in lower case, because GitHub serves a project site at its repository name under the organisation's domain and the path is case-sensitive. It is also why the @@ -110,20 +110,12 @@ something that will not fit. ## Why the library sits in a subdirectory -The library is a Gradle **subproject** rather than the root project. That reads as redundant and is not, -for a reason that is cheaper to keep than to change: +`SimLoop/` is the Android library subproject; the root build coordinates it with the starter examples +and desktop keyboard window. Gradle tasks therefore use the `:SimLoop` prefix. -- The Android Gradle Plugin's library plugin is applied to something it expects. A root project that is - itself an Android library is a layout nothing else in the FTC ecosystem uses. -What does **not** depend on it is the published coordinate, which is worth saying because this file used -to claim the opposite. JitPack names the artifact after the repository, not after the Gradle module, as -long as the build publishes exactly one module — measured, not assumed: the first build of -`v0.1.0-beta1` served `com.github.Horizon-36596:simloop`. What would rename it is publishing a **second** -module, which switches JitPack to its multi-module spelling. So the rule to keep is "one published -module", not "one particular directory layout". - -So `SimLoop/` in a path and `:SimLoop` in a Gradle task are correct everywhere they appear, including in -the CI workflows. +The published coordinate follows the repository name, `com.github.Horizon-36596:simloop`. Keep one +published module: the desktop jar is a classifier of that same publication, not a second module. +Publishing a second module would change JitPack's artifact naming. ## Building and testing it @@ -133,20 +125,18 @@ From the repository root: ./gradlew test ``` -1,218 JVM tests (1,212 in the library, 4 in the keyboard-window module, 2 in the example robot), with no -device and no emulator. The -documentation site and the API reference: +The library, keyboard-window and starter-example tests run on the JVM, with no device or emulator. +To build the documentation site and API reference: ```bash pip install -r SimLoop/docs-requirements.txt ./gradlew :SimLoop:docsSite ``` -All of it is built on every push and every pull request by -[`.github/workflows/test.yml`](.github/workflows/test.yml), so none of it can quietly become something -that only builds on one laptop. Pull requests also run a leak check -([`tools/leak-gate/`](tools/leak-gate/leak-gate.sh)) and a secret scan -([`.github/workflows/leak-gate.yml`](.github/workflows/leak-gate.yml)). +[`.github/workflows/test.yml`](.github/workflows/test.yml) defines the automated test and documentation +checks. Pull-request checks also include the [leak gate](tools/leak-gate/leak-gate.sh) and +[secret scan](.github/workflows/leak-gate.yml). Check the workflow results for the commit you use; the +commands above run the tests and documentation build locally. ## Licence diff --git a/SimLoop/PUBLISHING.md b/SimLoop/PUBLISHING.md index 8a8aa1f..074a2fc 100644 --- a/SimLoop/PUBLISHING.md +++ b/SimLoop/PUBLISHING.md @@ -189,12 +189,10 @@ the path is `/simloop`. It was renamed from `SimLoop` on 2026-09-19 for exactly tag existed — which also moved the published coordinate to `com.github.Horizon-36596:simloop`, since JitPack takes the artifact id straight from the repository name. -> **Steps 1 and 4 are done.** Both were done on 2026-09-19 and neither needs doing again. The two that -> remain — **2** and **3** — are the DNS record and the custom domain, and they are the two nobody but a -> human with the domain's credentials can do. Until they are done, both sites are already live at -> GitHub's own addresses: and -> . Steps 2 and 3 move them onto `libraries.horizon36596.org`; -> they do not turn them on. +The landing repository was created on 2026-09-19. Check the current DNS, Pages settings and latest +deployment before repeating setup steps. On 2026-10-24 the custom domain answered, but `/simloop/` +and `/simloop/javadoc/` returned HTTP 404 while this repository was private. The source and a successful +local build do not establish that the project site is deployed. **1. Create the landing repository.** ✅ **Done 2026-09-19** — `Horizon-36596/Horizon-36596.github.io`, public, with the landing page at its root and Pages serving `main` / `(root)`. The name is not a choice: @@ -289,12 +287,14 @@ and depends on your registrar. Both are waiting, not failure. If this one fails, nothing below it can work — the domain is not attached yet. The github.io addresses keep working throughout; they are the same site, and comparing the two is how you tell a DNS problem from a site problem. -- `https://libraries.horizon36596.org/simloop/` loads the documentation site. A 404 here with the landing - page working means either the tag has not been pushed, or a custom domain got set on this repository - after all. +- `https://libraries.horizon36596.org/simloop/` loads the documentation site. If the landing page works + but this path returns 404, check this repository's Pages configuration, visibility, workflow result + and published artifact. Also confirm that no separate custom domain is set on this project site. - The footer of every page reads the version you just tagged. -- `https://libraries.horizon36596.org/simloop/javadoc/` loads the API reference. If the site loads and - this 404s, the `docsSite` task did not run — the deploy published a bare `mkdocs build` from somewhere. +- `https://libraries.horizon36596.org/simloop/api-reference/` loads the API guide page, and its button + opens `https://libraries.horizon36596.org/simloop/javadoc/`. Check that both routes exist in the + artifact. If only Javadoc is missing, verify that the deployment used `docsSite`, which copies it, + rather than MkDocs alone. ## What a Maven Central release would additionally need diff --git a/SimLoop/build.gradle b/SimLoop/build.gradle index 26637b6..afc0a0d 100644 --- a/SimLoop/build.gradle +++ b/SimLoop/build.gradle @@ -395,11 +395,18 @@ tasks.register('checkDocsVersion') { // `--strict` turns a broken internal link into a failed build. A documentation site's most common defect // is a link that goes nowhere, and it is invisible to everyone except the reader who followed it. +tasks.register('checkDocExamples', Exec) { + group = 'documentation' + description = 'Checks that the complete tutorial example matches its compiled test source.' + workingDir projectDir + commandLine 'python', 'docs-tools/check-examples.py' +} + tasks.register('mkdocsBuild', Exec) { group = 'documentation' description = 'Renders the markdown pages. Needs, in this directory: pip install -r docs-requirements.txt' - dependsOn 'checkDocsVersion' + dependsOn 'checkDocsVersion', 'checkDocExamples' workingDir projectDir commandLine 'python', '-m', 'mkdocs', 'build', '-f', 'mkdocs.yml', '--strict' diff --git a/SimLoop/docs-tools/check-examples.py b/SimLoop/docs-tools/check-examples.py new file mode 100644 index 0000000..d8ade00 --- /dev/null +++ b/SimLoop/docs-tools/check-examples.py @@ -0,0 +1,36 @@ +"""Keep the tutorial's complete Java example aligned with the test that compiles it.""" + +from pathlib import Path +import argparse +import difflib +import re +import sys + + +module = Path(__file__).resolve().parents[1] +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument("--document", type=Path, default=module / "docs" / "first-test.md") +args = parser.parse_args() +source_path = module / "src/test/java/org/horizon36596/simloop/example/SlideReachesTargetExampleTest.java" +source = source_path.read_text(encoding="utf-8") +document = args.document.read_text(encoding="utf-8") +match = re.search(r"^```java\n(.*?)\n```$", document, flags=re.M | re.S) +if match is None: + sys.exit("first-test.md has no complete Java example.") + +# The guide omits only the class-level Javadoc describing where the example is published. +source = re.sub( + r"/\*\*.*?\*/\s*(?=class SlideReachesTargetExampleTest\b)", "", source, count=1, flags=re.S +).strip() +example = match.group(1).strip() +if example != source: + diff = difflib.unified_diff( + source.splitlines(keepends=True), + example.splitlines(keepends=True), + fromfile="SlideReachesTargetExampleTest.java", + tofile="first-test.md Java block", + ) + sys.stderr.write("The tutorial differs from its compiled example. Update both files.\n") + sys.stderr.writelines(diff) + sys.exit(1) +print("first-test.md matches its compiled Java example.") diff --git a/SimLoop/docs/agents.md b/SimLoop/docs/agents.md index e0e843e..6ece899 100644 --- a/SimLoop/docs/agents.md +++ b/SimLoop/docs/agents.md @@ -34,7 +34,7 @@ Horizon uses **Claude Code**; its paths are the ones the team has exercised most | `skills/` | Four procedures — write a sim test, add a mechanism, debug a failing sim, review sim code. Written for any agent; drop into `.claude/skills/` unmodified. | | `prompts/` | Copy-paste starting points, including one for learning and one for auditing your tests before a competition. | | `pointers/` | One-line files for the tools that need a nudge, and for a project that already has its own instructions file. They point at `AGENTS.md` rather than copying it. | -| `token-savers.md` | Spending less. Techniques first, because they are free and work better than the tools. | +| `token-savers.md` | Ways to keep context focused and compare usage without assuming fixed savings. | ## The rules an agent gets wrong diff --git a/SimLoop/docs/api-reference.md b/SimLoop/docs/api-reference.md new file mode 100644 index 0000000..bfac2a9 --- /dev/null +++ b/SimLoop/docs/api-reference.md @@ -0,0 +1,29 @@ +# API reference + +Every public class, method and field in `org.horizon36596.simloop`, with **units and frame stated on +every number**. + +Open the API reference + +That link opens the generated Javadoc beside this guide, both on the published site and in a full +local build. Build the guide and API reference together from the repository root: + +```powershell +./gradlew :SimLoop:docsSite +``` + +Then open `SimLoop/build/docs/site/api-reference/index.html`. Running MkDocs alone builds the guide +but does not copy the API pages. For Javadoc alone, run `:SimLoop:apiDocs` and open +`SimLoop/build/docs/apiDocs/index.html`. + +(Run it from the repository root, not from inside `SimLoop/`. The library is a Gradle subproject of a +repository that holds only it, so the `:SimLoop` prefix is correct.) + +## How it is kept complete + +Javadoc is generated with `-Xdoclint:all` and `-Werror`. Missing comments, parameters, return +descriptions and broken `{@link}` references fail the build. Units and coordinate frames belong in +the public API comments so callers do not have to infer them from a method name. + +The same build ships in every release as `SimLoop--javadoc.jar`, so the pages your IDE shows and +the pages on this site are the same build. diff --git a/SimLoop/docs/first-test.md b/SimLoop/docs/first-test.md index 75f6e44..eee74c0 100644 --- a/SimLoop/docs/first-test.md +++ b/SimLoop/docs/first-test.md @@ -8,6 +8,9 @@ The maintained example is It runs with the library's tests. Use the source at your dependency's tag if a snippet here differs from the version you installed. +The documentation build also checks that the complete Java block below matches that test, apart +from its class-level documentation comment. A change to one must update the other. + ## The five things a simulated test always has Whatever you are testing, the shape is the same: diff --git a/SimLoop/docs/getting-started.md b/SimLoop/docs/getting-started.md index cfb73e7..f500f0e 100644 --- a/SimLoop/docs/getting-started.md +++ b/SimLoop/docs/getting-started.md @@ -15,10 +15,13 @@ build settings if you want to maintain them yourself. ## What you need first -- **Java 17 or newer** to run Gradle. Android Studio ships one. +- **A JDK compatible with your project's Gradle wrapper.** Java 17 is the baseline for this build; + Android Studio includes a JDK, but its version must still match what your wrapper supports. - **An FTC SDK 12.0 project.** `RobotCore` and `Hardware` at `12.0.0`. +- **The Android SDK required by that project**, installed and configured for Gradle. A plain JVM test + still builds through the Android Gradle Plugin; it does not need an Android emulator. -That is the whole list. You do **not** need to clone this repository, and you do not need SolversLib, +You do **not** need to clone this repository, and you do not need SolversLib, Pedro Pathing, Road Runner, FTC Dashboard or Robolectric. SimLoop depends on none of them. ### Why SDK 12.0 diff --git a/SimLoop/docs/index.md b/SimLoop/docs/index.md index a480bd7..c389020 100644 --- a/SimLoop/docs/index.md +++ b/SimLoop/docs/index.md @@ -93,10 +93,11 @@ These are enforced by tests in the module, not by good intentions. - **No season imports.** No file in `org.horizon36596.simloop` imports `org.firstinspires.ftc.teamcode`. Season facts arrive through the `config` interfaces. - **Depends only on** the FTC SDK 12.0 and PsiKit. No SolversLib, no follower library. -- **Deterministic.** No wall-clock reads on any sim path. `update(deltaTime)` mutates state; getters are - pure reads. -- **The fakes replicate real device semantics exactly** — every method of the interface, including the - ones nobody calls, behaving the way the hardware does. +- **Deterministic model time.** Plants advance from the supplied `deltaTime`; getters are pure reads. + I/O waits and live pacing do not advance that clock. Robot code must use sim time too. +- **SDK-facing fakes.** Robot code uses FTC SDK types. The fake devices implement their state and + control interfaces; readings come from the simulation model. See the + [hardware guide](packages/fakehardware.md) for behavior and limits. ## Help build SimLoop diff --git a/SimLoop/docs/javadoc.md b/SimLoop/docs/javadoc.md deleted file mode 100644 index b1c13a3..0000000 --- a/SimLoop/docs/javadoc.md +++ /dev/null @@ -1,30 +0,0 @@ -# API reference - -Every public class, method and field in `org.horizon36596.simloop`, with **units and frame stated on -every number**. - -[Open the API reference](/javadoc/index.html){ .md-button .md-button--primary } - -That link works on the published site, where the javadoc is copied in beside these pages by the site -build. If you are reading this locally after `mkdocs build` alone, generate the javadoc first: - -```powershell -./gradlew :SimLoop:apiDocs -``` - -and open `SimLoop/build/docs/apiDocs/index.html` directly. - -(Run it from the repository root, not from inside `SimLoop/`. The library is a Gradle subproject of a -repository that holds only it, so the `:SimLoop` prefix is correct.) - -## How it is kept complete - -The javadoc is generated with `-Xdoclint:all` and `-Werror`, so a public member with no comment, a missing -`@param` or `@return`, or a broken `{@link}` **fails the build**. It is not a report somebody is supposed -to read; it is a gate. - -That matters more here than in most libraries, because this reference is read by AI agents as well as -people, and an agent handed `setTarget(double)` with no unit will pick one. - -The same build ships in every release as `SimLoop--javadoc.jar`, so the pages your IDE shows and -the pages on this site are the same build. diff --git a/SimLoop/docs/packages/config.md b/SimLoop/docs/packages/config.md index c46f25a..4257803 100644 --- a/SimLoop/docs/packages/config.md +++ b/SimLoop/docs/packages/config.md @@ -2,8 +2,8 @@ `org.horizon36596.simloop.config` -The one-way seam. These are interfaces **you** implement, in your season repository, to tell SimLoop your -robot's numbers. SimLoop reads them; SimLoop never imports anything of yours. +Implement these interfaces in your robot project to supply its dimensions, motion limits and device +names. SimLoop reads that configuration without importing your robot or season code. ## What you implement diff --git a/SimLoop/docs/packages/fit.md b/SimLoop/docs/packages/fit.md index 423a31d..4d2899d 100644 --- a/SimLoop/docs/packages/fit.md +++ b/SimLoop/docs/packages/fit.md @@ -73,6 +73,10 @@ clean R squared and meaningless values. `Result.warnings()` flags that: a condit (`MAX_CONDITION_NUMBER`), or a standard error above 10% of the value (`MAX_RELATIVE_STANDARD_ERROR`). `isTrustworthy()` is true only when there are none. +That flag covers the fit's diagnostics. It does not establish that a first-order model represents +your mechanism or that it predicts a separate real-robot run. Compare against another recording +before using the fitted constants as a model of the robot. + Data where the driver drove open-loop, the controller saturated, or the setpoint changed separates the parameters. Give the fit that. diff --git a/SimLoop/docs/packages/live.md b/SimLoop/docs/packages/live.md index 66304d8..858d67f 100644 --- a/SimLoop/docs/packages/live.md +++ b/SimLoop/docs/packages/live.md @@ -3,8 +3,9 @@ `org.horizon36596.simloop.sim.live`, and the separate module `SimLoop/desktop/` (`org.horizon36596.simloop.desktop`) -Drive the simulated robot from your laptop keyboard in real time, record exactly what you pressed, and -replay that recording headless to the same log, bit for bit. +Drive the simulated robot from your laptop keyboard, record the input for each tick, and replay it +headless to reproduce the robot-authored log values. Compare decoded values with `RlogDecodedCompare`; +raw RLOG bytes and PsiKit's writer metadata need not match. ## What you use diff --git a/SimLoop/docs/packages/simclock.md b/SimLoop/docs/packages/simclock.md index 52d151e..0c6f50a 100644 --- a/SimLoop/docs/packages/simclock.md +++ b/SimLoop/docs/packages/simclock.md @@ -7,10 +7,10 @@ directly: `OpModeRunner.forTeamClass(...)` in [`opmode`](opmode.md) uses it for ## Why it exists -A sim tick simulates 20 ms and takes well under a millisecond of laptop time. Code that reads the laptop's -clock (`new ElapsedTime()`, `System.nanoTime()`, `Thread.sleep`) therefore waits far too long in sim time, -and a different amount every run. This package loads your classes so those calls read the sim clock -instead. +A default tick advances 20 ms of simulated time, regardless of how long it takes on the laptop. +Code that reads the laptop's clock (`new ElapsedTime()`, `System.nanoTime()`) or waits with +`Thread.sleep` can therefore behave differently between runs. This package loads your team's classes +with supported clock reads and sleeps rewritten to use sim time. ## How you use it diff --git a/SimLoop/docs/packages/vision.md b/SimLoop/docs/packages/vision.md index f5e3236..1a49611 100644 --- a/SimLoop/docs/packages/vision.md +++ b/SimLoop/docs/packages/vision.md @@ -110,6 +110,10 @@ CameraOccluders blockers = CameraOccluders.of( List seen = camera.observe(tracker.getPieces(), x, y, heading, blockers); ``` +`CameraOccluders` copies both lists. Robot-frame boxes turn with the chassis, but an arm's joint +motion does not update them automatically. Build new occluders when a mechanism or field structure +changes shape or position. + Each piece gets 19 rays spread over its disc. `getVisibleFraction()` is the share not blocked by a box or a nearer piece, and a piece below `getMinVisibleFraction()` (default 0.5, `withMinVisibleFraction(...)` to change) is not reported. The four-argument `observe` keeps the old rule: nothing but other pieces' diff --git a/SimLoop/docs/sim-safe-timers.md b/SimLoop/docs/sim-safe-timers.md index 68ee85d..059e495 100644 --- a/SimLoop/docs/sim-safe-timers.md +++ b/SimLoop/docs/sim-safe-timers.md @@ -11,7 +11,7 @@ You do not need to read the rest of the SimLoop docs first. You need to know wha On the robot there is one clock: real time. In a SimLoop test there are two. The **sim clock** is a `FakeTimer`, and SimLoop moves it forward by a fixed step each tick - usually 20 ms, the same as one loop on a Control Hub. The **wall clock** is your laptop's real time, and it keeps running at its own speed. A -laptop does one 20 ms tick in well under a millisecond, so the two clocks drift apart fast. Everything the +laptop may advance many sim ticks during one real millisecond, or take longer on a busy run. Everything the sim simulates - motors turning, mechanisms moving, the robot driving - follows the sim clock. So any code that looks at the wall clock is looking at the wrong clock. A 250 ms wait that reads the wall clock can last 30 *simulated* seconds, and a different number every run. (That is a real measurement: it happened diff --git a/SimLoop/docs/troubleshooting.md b/SimLoop/docs/troubleshooting.md index a3f2d30..81e6154 100644 --- a/SimLoop/docs/troubleshooting.md +++ b/SimLoop/docs/troubleshooting.md @@ -40,8 +40,10 @@ check for an `exclude` or a platform constraint on `org.firstinspires.ftc`. ## `SDK location not found` -Nothing to do with SimLoop. A Gradle build in a fresh checkout or a new git worktree needs -`local.properties`, which is not in version control. Copy it from a working checkout. +Gradle cannot find the Android SDK. The tests run on the JVM, but the Android project still needs its +SDK installed and configured. On the same machine, a fresh checkout or worktree can use +`local.properties` from a working checkout. On another machine, set `sdk.dir` to that machine's SDK +location; do not copy someone else's absolute path. Keep this file out of version control. ## The mechanism moves a tick late, and no gain fixes it diff --git a/SimLoop/docs/unchanged-team-code.md b/SimLoop/docs/unchanged-team-code.md index 2d78394..2cb05e4 100644 --- a/SimLoop/docs/unchanged-team-code.md +++ b/SimLoop/docs/unchanged-team-code.md @@ -12,11 +12,11 @@ You need to know how to write a JUnit test and what `ElapsedTime` is, and your b A SimLoop test has its own clock, the **sim clock**. It moves forward one tick at a time, usually 20 ms, the length of one loop on a Control Hub. Motors, mechanisms and the robot's position all follow the sim -clock. Your laptop runs a tick in well under a millisecond, so the sim clock races ahead of real time. +clock. A tick's wall-clock duration depends on the work it does and the laptop running it. -That is good (a 30 second auto takes a fraction of a second), but it breaks code that reads the **real** -clock. A plain `new ElapsedTime()` measures laptop time, so "wait 500 ms" lasts 500 ms of laptop time, which -is hundreds of sim ticks, and a different number every run. +Code that reads the **real** clock therefore measures a different duration from the simulation. +A plain `new ElapsedTime()` measures laptop time: "wait 500 ms" can span a different number of sim +ticks on each run. `OpModeRunner.forTeamClass(...)` fixes this. It loads your classes in a special way so that every clock call in them reads the sim clock instead. @@ -298,7 +298,8 @@ should be compiled for Java 8. Rebuild, and if it still fails, report it with th Yes, for your own code: the same OpMode, gamepad script and fake hardware give the same telemetry, the same motor positions and an RLOG that decodes to the same values every time, however fast or slow the computer -is. A 10 second `sleep` takes a few milliseconds of real time. The exceptions are the things listed under +is. A rewritten `sleep` advances by sim ticks rather than waiting for its duration on the laptop. +The exceptions are the things listed under [What does not change](#what-does-not-change): `getRuntime()` in telemetry, the real date and time, and any library outside your team packages that reads the clock, such as Pedro Pathing under the default packages. The RLOG's raw bytes differ from run to run, because PsiKit logs its own writer queue diff --git a/SimLoop/mkdocs.yml b/SimLoop/mkdocs.yml index 2eb07b4..22df8a7 100644 --- a/SimLoop/mkdocs.yml +++ b/SimLoop/mkdocs.yml @@ -128,7 +128,7 @@ nav: - Working with AI agents: - Using SimLoop with an AI agent: agents.md - API reference: - - Every class, method and field: javadoc.md + - Every class, method and field: api-reference.md - Help: - Troubleshooting: troubleshooting.md - Reporting a bug: support.md diff --git a/docs/documentation-review-2026-10-24.md b/docs/documentation-review-2026-10-24.md index b9904db..f3db976 100644 --- a/docs/documentation-review-2026-10-24.md +++ b/docs/documentation-review-2026-10-24.md @@ -14,7 +14,8 @@ that those classes resolve from the pinned alpha coordinate. ## Changes made - Marked the source/tag boundary on the home page, feature list, vision page, agent index and README. -- Linked installation to the starter at the pinned tag and explained first-run dependency downloads. +- Linked installation to the starter at the pinned tag, added the Android SDK prerequisite and + explained first-run dependency downloads. - Clarified that the installed starter tests and the slide tutorial are different examples. - Added vision output selection, channel order, native OpenCV setup, processor initialization, calibration requirements, Mat cleanup and deterministic frame-sequence guidance. @@ -22,8 +23,13 @@ that those classes resolve from the pinned alpha coordinate. - Added logging ownership and corrected the distinction between model time and I/O waits. - Expanded nondeterminism troubleshooting beyond wall-clock reads. - Replaced broad claims about automatic tool support and repeated agent advice with explicit setup and - review steps. Simplified the toolkit README without changing its workflow. + review steps. Added the missing pointers README and replaced unsupported cost-saving percentages + and benchmark claims with a usage-measurement workflow. - Added a plot of a real run of the documented slide test, its exported values and regeneration tools. +- Moved the API landing page to `api-reference/` so the Javadoc copy cannot overwrite it, and fixed + its generated-reference link to work under the site's `/simloop/` path. +- Added a documentation-build check that compares the complete slide tutorial with its compiled + example, so changes cannot leave an older snippet behind unnoticed. ## Verification @@ -33,8 +39,11 @@ that those classes resolve from the pinned alpha coordinate. 13.150056536 in. These pass the tutorial's 0.25 in final tolerance and 14 in peak bound. - The tutorial's executable Java matches the maintained test source; the only omitted block is the class-level documentation comment. -- Local file and anchor crawl passed for all 29 rendered guide pages before the final additions; - the final pass is recorded in the PR. Generated Javadoc is checked by `docsSite`. +- Local file, image and anchor crawl passes across the rendered guide and its generated-reference + targets. The API and slide-log pages were rendered in Chrome with no page overflow. Exact final + link counts are recorded in the PR. +- The tutorial source checker passes on the guide and fails on a copy with one changed tick count. +- The repository leak gate passes all 10 rules, including the added plot and CSV. The plot describes simulated values from the example's placeholder model, not measured robot motion. The OpenCV fragment requires the consuming team's processor and native library; it is not a standalone @@ -45,3 +54,11 @@ test, and no end-to-end OpenCV execution is claimed for this patch. #21 adds the long-log streaming documentation. #22 owns the logger-reset fix. #23 owns the counter and privacy page; #25 owns the logo assets. Keep their documentation with their code when merging. A published tag containing #24 must be chosen separately; this patch changes no version or release. + +## Published site check + +Live retrieval on 2026-10-24 returned HTTP 404 from both +`https://libraries.horizon36596.org/simloop/` and its `/javadoc/` route. The connected GitHub repository +was private at that check. The README no longer claims the site is live, and publishing instructions +now check Pages settings, repository visibility, workflow status and the actual artifact before +diagnosing a 404. This patch does not publish the site or change repository visibility. diff --git a/simloop-agents/README.md b/simloop-agents/README.md index 8ceefc2..d878b01 100644 --- a/simloop-agents/README.md +++ b/simloop-agents/README.md @@ -83,7 +83,7 @@ agent has loaded both the SimLoop guidance and your robot-specific instructions. | [`skills/`](skills) | Four procedures: write a sim test, add a mechanism, debug a failing sim, review sim code. Written for any agent; drop straight into `.claude/skills/` unmodified. | | [`prompts/`](prompts/README.md) | Copy-paste starting points, including one for learning and one for auditing your tests before a competition. | | [`pointers/`](pointers) | One-line files for the tools that need a nudge, and for a project that already has its own instructions file. Deliberately thin — they point at the one copy rather than duplicating it. | -| [`token-savers.md`](token-savers.md) | Spending less. Techniques first, because they are free and work better than the tools. | +| [`token-savers.md`](token-savers.md) | Ways to keep context focused and compare usage without assuming fixed savings. | ## One thing you will need that is not in here diff --git a/simloop-agents/pointers/CLAUDE.md b/simloop-agents/pointers/CLAUDE.md index ecc4d08..eb72321 100644 --- a/simloop-agents/pointers/CLAUDE.md +++ b/simloop-agents/pointers/CLAUDE.md @@ -1,11 +1,7 @@ + If your project already has a CLAUDE.md, do not replace it. Add the @-import line below to it. + Keep the SimLoop guidance in one AGENTS.md and keep your team's existing instructions. + Confirm that both files are loaded in the session. --> @AGENTS.md diff --git a/simloop-agents/pointers/README.md b/simloop-agents/pointers/README.md new file mode 100644 index 0000000..9e19621 --- /dev/null +++ b/simloop-agents/pointers/README.md @@ -0,0 +1,18 @@ +# Point your tool at the shared instructions + +Keep the SimLoop guidance in the root `AGENTS.md` of your FTC project. These files are examples for +tools that use another project-instructions file. Confirm in the session that the agent has loaded +the shared guidance; do not assume that copying a filename was enough. + +| Example | Where it belongs in your FTC project | What to copy | +|---|---|---| +| [`CLAUDE.md`](CLAUDE.md) | Root `CLAUDE.md` | The `@AGENTS.md` import line | +| [`copilot-instructions.md`](copilot-instructions.md) | `.github/copilot-instructions.md` | The instruction to read root `AGENTS.md` | + +If the destination already exists, add the pointer to it and keep the team's current instructions. +Do not replace the file with this example. Check your tool's current instructions mechanism if it +uses a different path or import syntax. + +After setup, ask the agent to identify your pinned SimLoop version, the units your mechanisms use, +and the rule about `RealOutputs/` log keys. If it has not loaded those facts, fix the pointer before +asking it to write a test. diff --git a/simloop-agents/pointers/copilot-instructions.md b/simloop-agents/pointers/copilot-instructions.md index ba00e2d..2cd8c22 100644 --- a/simloop-agents/pointers/copilot-instructions.md +++ b/simloop-agents/pointers/copilot-instructions.md @@ -1,17 +1,12 @@ + Use this pointer if the tool or project needs an explicit reference to AGENTS.md. If this + destination already exists, add the instruction without replacing the team's current guidance. + Confirm that the shared instructions are loaded in the session. --> This project uses **SimLoop** to run its robot code against fake hardware in JVM unit tests. Read `AGENTS.md` in the root of this repository and follow it. It has the rules that matter: the log-key prefix, the tick order, the two coordinate frames, the units, and what SimLoop deliberately does not do. -Do not write a SimLoop test without reading it first. The mistakes it warns about all compile, and most -of them pass. +Read it before writing a SimLoop test, and check the project's pinned version before calling an API. diff --git a/simloop-agents/prompts/README.md b/simloop-agents/prompts/README.md index 0bd9232..6185846 100644 --- a/simloop-agents/prompts/README.md +++ b/simloop-agents/prompts/README.md @@ -4,8 +4,8 @@ Copy one of these, paste it into whatever agent you use, and fill in the ` Read `TeamCode/.../Slide.java`. Find why the slide overshoots, preserve its hardware names and +> units, and verify the change with the existing slide test. -### Point at the thing, do not paste the repository +Include the exact error and relevant test output. Keep the full log available, but start with the +failure and its surrounding lines rather than pasting every successful build step. -"Read `TeamCode/.../Slide.java` and fix the overshoot" costs a fraction of "here is my project, fix the -overshoot", and the answer is better, because the agent is not choosing between forty files. +## Keep the facts easy to find -### Start a new conversation per task +Put shared project facts in `AGENTS.md`: dependency versions, hardware names, units, tick order and +required checks. Confirm that the tool has loaded it. Link to the source of a fact instead of keeping +several copies that can disagree. -A long conversation resends its whole history with every message. Ten small tasks in one conversation -can cost several times ten small conversations. When the subject changes, start over. +When you already know the relevant file or symbol, name it. When you do not, let the agent inspect +the dependencies it needs; an artificially narrow view can produce an incomplete fix. -### Put the standing facts in a file, not in every message +## Keep the context relevant -That is what `AGENTS.md` in this folder is for. Written once, read automatically, never retyped. Re-typing -your conventions at the top of each request is the same tokens over and over. +Use a separate conversation for an unrelated task when the earlier history no longer helps. Keep +an ongoing fix in the same conversation while its decisions and evidence are still useful. A reset +that forces the agent to rediscover the project can waste work too. -### Ask for the shape of the answer you want +Before handing work to a fresh conversation, save the changed files, current hypothesis, exact +commands and results, and what remains unresolved. A short handoff is more useful than a transcript +of every attempt. -"One line per finding, no preamble" genuinely costs less than letting a model write four paragraphs of -throat-clearing first. Output is the expensive half per token. +## Ask for the output you need -### Do not make an agent search for something you already know +For a code review, ask for actionable findings with file locations and evidence. For a completed +change, ask what changed, which checks ran and any remaining limitation. Shortening the report can +help without removing the investigation or required verification. -If you know the file, say the file. If you know the line, say the line. A five-second look on your part -removes ten tool calls on its. +## Measure changes on comparable work -### Let it fail fast +If you try a different model, context setting or output-compression tool, compare it on similar +jobs. Record usage, retries and whether the result was correct. Smaller output alone does not show +that the whole task cost less. -Give the agent the exact error text. An agent that has to reproduce a failure itself spends a build, a -test run, and a lot of reading to arrive where your clipboard already was. - -## The tools - -### RTK (Rust Token Killer) - - - -A CLI proxy. The agent runs `git status`, RTK runs the real command, and hands back a compressed version -of the output instead of the raw wall of text. A single Rust binary with no dependencies; on Claude Code -it installs a hook that rewrites eligible shell commands transparently, so nothing in your workflow -changes. - -**What it claims:** 60–90% fewer tokens on common dev commands. - -**What is contested:** an independent benchmark published by JetBrains measured it as *more* expensive -on real agent work at low reasoning effort and about neutral at high effort. The compression is real; -whether it saves you money depends on how much of your session is actually shell output. - -So: worth trying, worth **measuring on your own usage**, not worth treating as a guaranteed saving. It -also only intercepts shell commands — an agent's built-in file-reading and search tools do not go -through it, and on a typical FTC session those are most of the traffic. - -### Your tool's own context controls - -Cheaper than any proxy and already installed. Most agents have a way to clear or compact the -conversation; in Claude Code that is `/clear` between tasks and `/compact` when one task genuinely needs -the history. Learn the two commands for whichever tool you use. This is the single biggest lever on the -list. - -### A smaller model for the small jobs - -Renaming things, writing javadoc, formatting a table, summarising a log — these do not need the most -capable model available. Most tools let you switch. The difference in cost between tiers is large and -the difference in quality on a mechanical task is not. - ---- - -## Measure before you optimise - -Both of the above sell a number. Yours will be different. Run a normal week, look at what your tool -reports you actually spent, and change one thing at a time. - -An FTC team's bill is usually not dominated by clever tooling — it is dominated by long conversations -that should have been several short ones. Fix that first and you may not need any of this. +Change one thing at a time. Keep the configuration that completes the work reliably within your +team's budget, using the current pricing and limits shown by your provider. From f7616cc5f42fa3b503c90072c6e86049cd096873 Mon Sep 17 00:00:00 2001 From: Saket T Date: Sat, 24 Oct 2026 05:43:25 -0700 Subject: [PATCH 3/6] docs: correct model explanations and release verification --- SimLoop/PUBLISHING.md | 77 +++++++++++++------------ SimLoop/build.gradle | 6 +- SimLoop/docs/api-reference.md | 17 ++++-- SimLoop/docs/getting-started.md | 66 ++++++++++----------- SimLoop/docs/index.md | 13 ++--- SimLoop/docs/llms.md | 2 +- SimLoop/docs/packages/config.md | 11 ++-- SimLoop/docs/packages/fakehardware.md | 17 +++--- SimLoop/docs/packages/plant.md | 13 ++--- SimLoop/docs/tested.md | 4 +- docs/documentation-review-2026-10-24.md | 14 ++++- 11 files changed, 126 insertions(+), 114 deletions(-) diff --git a/SimLoop/PUBLISHING.md b/SimLoop/PUBLISHING.md index 074a2fc..1d02987 100644 --- a/SimLoop/PUBLISHING.md +++ b/SimLoop/PUBLISHING.md @@ -15,9 +15,9 @@ Central. The build wiring this describes lives at the bottom of `SimLoop/build.g **JitPack ignores every one of those first three values and derives its own.** The group becomes `com.github.`, the artifact becomes the **repository** name, and the version is the git tag -verbatim, leading `v` included. So a consumer writes `com.github.Horizon-36596:simloop:v0.2.0-alpha.1` -while the POM inside says `org.horizon36596:SimLoop:0.2.0-alpha.1`, and both are correct at once — the -declared values are what `publishToMavenLocal` and any future Maven Central release use. +verbatim, leading `v` included. A consumer writes `com.github.Horizon-36596:simloop:v0.2.0-alpha.1`; +JitPack's served POM uses that coordinate too. The declared `org.horizon36596:SimLoop:0.2.0-alpha.1` +coordinate belongs to `publishToMavenLocal` and a possible Maven Central publication. That is JitPack's **single-module** spelling, and this build gets it because `jitpack.yml` installs exactly one module. A build that published two would get the **multi-module** spelling, @@ -58,10 +58,9 @@ except where a row says it was overtaken. | `EnvelopeGuardrails` guardrail names | `withinBound`, `loopTimeUnderBudget` and `safeStopAtEnd` now embed their thresholds in `Guardrail.name()`. Any persisted last-best score from an earlier version will read as a different envelope in `Gate` and must be re-baselined. Non-finite and negative thresholds are now rejected at construction. | | `RunResult.fromRlog` | `RealOutputs/Console` and `RealOutputs/Logger/QueuedCycles` are no longer scoreable, so `keys()` and `frames()` no longer contain them. | -`SimLoop/build.gradle` is the only place the version is *authoritative*, but it is not the only place -it is written: `README.md`'s install snippet, the coordinates table above and the verification note -below all quote it, and none of them is generated. Step 2 of the checklist below exists because of -that — a bump that misses the README leaves teams copying a version that was never tagged. +`SimLoop/build.gradle` declares the source version. Installation snippets, the starter, agent guides +and the site footer repeat it. Review all current-version references during a release; keep references +to older tags where they explain compatibility or historical checks. ## Cutting a release @@ -69,24 +68,24 @@ that — a bump that misses the README leaves teams copying a version that was n Then `./gradlew :SimLoop:apiDocs`, which must report **zero warnings**: the API reference is published and read by people and agents who have nothing else, so a missing unit is a release defect there in a way it is not in an IDE. -2. **Bump** `version` in `SimLoop/build.gradle`, **and the two copies of it**: `README.md`'s install - snippet, and the `copyright:` line in `SimLoop/mkdocs.yml`, which is the version stamped in the footer - of every documentation page. None of the three is linked to the others; see the note above. - `Select-String -Path SimLoop\README.md,SimLoop\mkdocs.yml -Pattern '0\.2\.0-alpha\.1'` finds them. +2. **Update the approved version** in `SimLoop/build.gradle` and its current-version references. + Search the root and module READMEs, `SimLoop/docs/`, `SimLoop/mkdocs.yml`, `simloop-starter/` and + `simloop-agents/`. Review each match rather than replacing historical tags blindly. Run + `./gradlew :SimLoop:docsSite`; its version check verifies the footer, and its example check verifies + the first-test snippet. These checks do not replace reviewing installation coordinates. 3. **Verify the artifact locally before anyone can consume it** — the section below says how, and it is the part people skip. 4. **Commit, tag, push the tag.** The tag is the version, e.g. `v0.2.0-alpha.1`. 5. **Build that tag on JitPack yourself, and read the log**, at - `https://jitpack.io/#Horizon-36596/simloop`. This is a gate, not a warm-up, and until it has passed - once nobody should be given the coordinate. Two specific things can only be learned here: + `https://jitpack.io/#Horizon-36596/simloop`. Before giving teams a new coordinate, verify: - **Whether the coordinate is right at all.** JitPack derives the artifact id from the module, and the only way to be certain which spelling it serves is to see it serve one. - - **Whether JitPack's environment builds it at all.** `jitpack.yml` pins `openjdk17` because the - Android Gradle Plugin requires it and JitPack's default is older. That pin has never been exercised - by a real JitPack build; the first tag is what exercises it. + - **Whether JitPack's environment builds the tag.** `jitpack.yml` pins `openjdk17`. Check the new + build's log rather than assuming a local build proves the hosted environment works. - The repository is public, which is what JitPack's free tier requires - it does not build private - repositories. + Check repository visibility and JitPack access before relying on a new hosted build. GitHub reported + this repository as private on 2026-10-24; its historical public setting is not its current state. + The owner decides any visibility or hosting change separately. 6. **Tell the teams the coordinate and the two repository lines**, not just the coordinate. The PsiKit one is the thing that bites — see below. @@ -100,9 +99,9 @@ Tagging and pushing leave the machine, so they are a human's call, not an agent' writes the AAR, the sources jar, the POM and the Gradle module metadata into `~/.m2/repository/`. -Then check the POM lists **RobotCore, Hardware, psikit-core and psikit-ftc at `compile` scope**. If any -of them says `runtime`, someone changed an `api` back to `implementation` and consumers will not be -able to name the types they are handed. +Then check the POM lists **`RobotCore`, `Hardware` and PsiKit `core` at `compile` scope**. The current +build does not depend on PsiKit `ftc`. If a required dependency moves to `runtime`, a consumer can +lose the SDK types or logging API it needs while compiling tests. The real check is to compile something against it from outside this repo: a throwaway Android library module whose only declared dependency is `org.horizon36596:SimLoop:`, with `mavenLocal()` @@ -120,6 +119,12 @@ That was run for `0.1.0-beta1` and passed, and the same project with the depende `implementation` failed with `cannot access HardwareMap` — the scope claim above is measured, not assumed. +On 2026-10-24, JitPack's [alpha build-status API](https://jitpack.io/api/builds/com.github.Horizon-36596/simloop/v0.2.0-alpha.1) +reported `ok` for tag commit `9116406` with JDK 17. Its +[served POM](https://jitpack.io/com/github/Horizon-36596/simloop/v0.2.0-alpha.1/simloop-v0.2.0-alpha.1.pom) +returned HTTP 200 and listed those three dependencies at compile scope. This confirms the existing +tag's hosted metadata; it does not validate unpublished source or guarantee a future build's access. + ## Why the library is a subproject and not the root project This repository holds one library, and that library is a Gradle **subproject**. Both the `SimLoop/` path @@ -163,11 +168,10 @@ publishes, and drops the API reference inside the site at `/javadoc/`. It writes pip install -r SimLoop/docs-requirements.txt ``` -`.github/workflows/docs-publish.yml` publishes it on a `v*` tag. No tag has been cut, so it has never -run, and four settings still have to exist before a deployment can succeed - three of them in a -different repository, because the site is served under a shared domain. The CI job in -`test.yml` builds the site on every push and keeps it as a downloadable artifact regardless, so a break -in the site is caught by the push that causes it rather than by a release. +`.github/workflows/docs-publish.yml` is configured to publish on a `v*` tag. Existing tags do not prove +that the workflow deployed successfully; check its latest run and the actual site. The settings below +span this repository and the shared-domain landing repository. The `test.yml` workflow also builds the +site and uploads an artifact without deploying it. ### Turning it on — four steps, all of them a human's @@ -318,7 +322,8 @@ Not done, and not needed for an alpha with a handful of teams. For when it is: build, which is also why there is nothing to keep in sync. - A `repositories { maven { url = ...; credentials { ... } } }` block, with credentials read from an environment variable or `~/.gradle/gradle.properties` and never from a file in the repository. -- Releases become **immutable**. An alpha tag can be re-cut after a mistake; a Central version cannot. +- Central versions are **immutable**. Treat published alpha tags as immutable too: correct a mistake + with a new version so cached artifacts and source keep referring to the same code. The POM already carries everything else Central validates — name, description, url, licence, developer and SCM — so this is a known and bounded list, not an unknown. @@ -339,19 +344,17 @@ file assumes. `v` is what declares a commit usable, and everything downstream reacts to it. No workflow creates a tag. This is the same rule root `CLAUDE.md` applies to push, PR and merge — the act that leaves the machine is a human's. -- **The repository is public, and the documentation site is bound to that.** Made public on 2026-09-17; - the site goes public with the code or not at all, so there is deliberately no separate repository - holding the built site and no paid JitPack plan. What is still not true is that anything has been - *released*: until a `v*` tag exists, the JitPack coordinate resolves for nobody and - `publishToMavenLocal` is the only route that works. That is stated wherever it matters rather than - papered over. +- **The original plan tied the documentation site to public source.** The repository was made public + on 2026-09-17, but GitHub reported it as private on 2026-10-24, when the docs routes returned 404. + Published tags now exist and the alpha's JitPack metadata is available. Check current access before + the next release; do not infer visibility, publication or artifact access from the original plan. - **The library is a Gradle subproject of a repository that holds only it.** The reasoning is above, - under "Why the library is a subproject and not the root project"; the short version is that - flattening the layout would rename the published artifact. + under "Why the library is a subproject and not the root project". Publishing one module is what + preserves the JitPack artifact name; the directory name alone does not determine it. - **Documentation is not versioned** until the library is past alpha. One site, no version selector, with the version stamped on every page. -- **Bugs go to GitHub Issues, questions to GitHub Discussions,** both on `Horizon-36596/simloop`. Both - are public and leave a record the next person with the same problem can find. +- **Bugs go to GitHub Issues, questions to GitHub Discussions,** both on `Horizon-36596/simloop`. + Access follows the repository's current visibility and permissions. ### Settled on 2026-09-17: the package and the Maven group diff --git a/SimLoop/build.gradle b/SimLoop/build.gradle index afc0a0d..67370aa 100644 --- a/SimLoop/build.gradle +++ b/SimLoop/build.gradle @@ -393,8 +393,6 @@ tasks.register('checkDocsVersion') { } } -// `--strict` turns a broken internal link into a failed build. A documentation site's most common defect -// is a link that goes nowhere, and it is invisible to everyone except the reader who followed it. tasks.register('checkDocExamples', Exec) { group = 'documentation' description = 'Checks that the complete tutorial example matches its compiled test source.' @@ -402,6 +400,7 @@ tasks.register('checkDocExamples', Exec) { commandLine 'python', 'docs-tools/check-examples.py' } +// `--strict` fails the build on broken guide links. Generated Javadoc is copied by docsSite afterwards. tasks.register('mkdocsBuild', Exec) { group = 'documentation' description = 'Renders the markdown pages. Needs, in this directory: pip install -r docs-requirements.txt' @@ -433,7 +432,8 @@ tasks.register('docsSite', Copy) { def builtSiteDir = layout.buildDirectory.dir('docs/site').get().asFile doLast { logger.lifecycle('Documentation site built: ' + builtSiteDir) - logger.lifecycle('Read it with: python -m mkdocs serve - run from the SimLoop directory.') + logger.lifecycle('Preview the complete site: python -m http.server 8000 --bind 127.0.0.1 --directory "' + + builtSiteDir + '"') } } diff --git a/SimLoop/docs/api-reference.md b/SimLoop/docs/api-reference.md index bfac2a9..65d784b 100644 --- a/SimLoop/docs/api-reference.md +++ b/SimLoop/docs/api-reference.md @@ -12,9 +12,15 @@ local build. Build the guide and API reference together from the repository root ./gradlew :SimLoop:docsSite ``` -Then open `SimLoop/build/docs/site/api-reference/index.html`. Running MkDocs alone builds the guide -but does not copy the API pages. For Javadoc alone, run `:SimLoop:apiDocs` and open -`SimLoop/build/docs/apiDocs/index.html`. +Preview the complete build from the repository root: + +```powershell +python -m http.server 8000 --bind 127.0.0.1 --directory SimLoop/build/docs/site +``` + +Open [the local API landing page](http://127.0.0.1:8000/api-reference/). Stop the server with `Ctrl+C`. +Running `mkdocs serve` alone previews the guide but does not copy the API pages. For Javadoc alone, +run `:SimLoop:apiDocs` and open `SimLoop/build/docs/apiDocs/index.html`. (Run it from the repository root, not from inside `SimLoop/`. The library is a Gradle subproject of a repository that holds only it, so the `:SimLoop` prefix is correct.) @@ -25,5 +31,6 @@ Javadoc is generated with `-Xdoclint:all` and `-Werror`. Missing comments, param descriptions and broken `{@link}` references fail the build. Units and coordinate frames belong in the public API comments so callers do not have to infer them from a method name. -The same build ships in every release as `SimLoop--javadoc.jar`, so the pages your IDE shows and -the pages on this site are the same build. +The release's Javadoc jar is built from the same generated reference as that source checkout. Use +the jar for your installed tag when you need a matching API reference: this unversioned site can +describe development APIs newer than the published dependency. diff --git a/SimLoop/docs/getting-started.md b/SimLoop/docs/getting-started.md index f500f0e..8368df5 100644 --- a/SimLoop/docs/getting-started.md +++ b/SimLoop/docs/getting-started.md @@ -90,9 +90,8 @@ repositories { !!! note "Why the Dairy line is not optional" SimLoop depends on PsiKit, and PsiKit is not on Maven Central. A POM can tell your build **what** a - dependency is, but never **where** to find it — that is always the consumer's repository list. Leave - the Dairy line out and resolution fails with `Could not find org.psilynx.psikit:core`, which reads - like SimLoop is broken and is not. + dependency is; Gradle resolves it from your project's repository list. Without the Dairy line, + resolution fails with `Could not find org.psilynx.psikit:core`. ### The dependency @@ -124,18 +123,16 @@ it belongs on the robot, and this scope is what keeps it out of the APK. !!! warning "The version string includes the `v`" JitPack's version **is** the git tag, letter for letter. The tag is `v0.2.0-alpha.1`, so the version is - `v0.2.0-alpha.1`. Ask for `0.2.0-alpha.1` and resolution fails with `Could not find`, which reads like a - missing library rather than a missing letter. + `v0.2.0-alpha.1`. Omitting the `v` requests a different version and can fail with `Could not find`. -!!! tip "That `junit-platform-launcher` line is not padding" - Gradle 8 put the JUnit Platform launcher on the test runtime classpath for you. Gradle 9 stopped. On - Gradle 9 without it, every test in the module fails before a single one runs, with - `Failed to load JUnit Platform`, which reads like a broken module rather than a build-tool change. +!!! tip "Declare the JUnit Platform launcher" + Gradle 9 removed the implicit test-framework dependencies that older Gradle builds could rely on. + Declare the launcher explicitly to avoid `Failed to load JUnit Platform`. See + [Gradle's test-runtime dependency guidance](https://docs.gradle.org/current/userguide/java_testing.html#sec:test_execution). -### The unit-test setting that is not a preference +### Avoid two test variants writing the same log -The starter also turns off the **release** unit-test variant, and this one is worth understanding before -you decide to leave it out: +The starter turns off the **release** unit-test variant: ```groovy androidComponents { @@ -145,27 +142,22 @@ androidComponents { } ``` -By default Android gives every build type its own unit-test task, so `./gradlew test` runs -`testDebugUnitTest` **and** `testReleaseUnitTest` in two separate JVMs sharing one working directory. -Every SimLoop scenario writes a log file into `build/sim/`, so two processes end up opening the same -file: on Windows the second delete fails while the first JVM holds the handle and both append, and on -Linux the delete succeeds and one JVM asserts on a truncated file. +Android creates unit-test tasks for each build type. `./gradlew test` can run both debug and release +tasks, potentially in separate JVMs. The starter examples use fixed paths in `build/sim/`; two tasks +writing the same path can corrupt or truncate a log, or fail to delete an open file. -The symptom is a determinism test failing with garbage that looks exactly like a determinism bug and is -not one — intermittently at first, then every time. `:TeamCode:testDebugUnitTest` stays green throughout, -so the two commands disagree and the one CI runs is the one that fails. - -Turning the release unit tests off is safe because unit tests are variant-independent: both variants -compile the same sources against the same dependencies, and no minification is applied to unit tests. +The starter needs only the debug tests. If your project has variant-specific code or dependencies +and needs both variants tested, keep both enabled and give each variant distinct log paths. Also +avoid overlapping runs of the same scenario. A test that passes alone but fails alongside another +writer is a reason to check file ownership before changing the model. ### Which scope SimLoop's own dependencies arrive at -SimLoop declares the FTC SDK (`RobotCore`, `Hardware`) and PsiKit as `api`, not `implementation`, so they -land on **your compile classpath** too. That is deliberate and not negotiable: SimLoop's public types are -SDK types — `FakeMotor` **is** a `DcMotorEx`, `FakeHardwareMap` **is** a `HardwareMap` — and you cannot -so much as name the type you got back without the SDK in front of you. +SimLoop declares the FTC SDK (`RobotCore`, `Hardware`) and PsiKit core as `api`, so those dependencies +reach **your test compile classpath** too. Public signatures expose SDK types; PsiKit core is also +available so your scenario can call `Logger.recordOutput(...)` without declaring a second version. -Those three are the whole dependency list. SimLoop pulls in nothing else. +Those are SimLoop's three direct dependencies; they can bring transitive dependencies of their own. ### Two test settings some fakes need @@ -186,10 +178,10 @@ dependencies { Without the first, `new RevHubOrientationOnRobot(...)` throws "not mocked". Without the second, every fake Limelight result is empty or throws. -## Building it yourself (works today) +## Building it yourself -This is the route that works before a tag exists, and it is also how you would test a change to SimLoop -itself before tagging it. It is the only route that needs a clone. +Clone the source to test an unreleased change or build APIs absent from the published tag. Run from +the repository root: ```powershell ./gradlew :SimLoop:publishToMavenLocal @@ -219,17 +211,17 @@ dependencies { `com.github.Horizon-36596`, the artifact becomes the repository name `simloop`, and the version is the tag verbatim — so a JitPack dependency is spelled `com.github.Horizon-36596:simloop:v0.2.0-alpha.1`. - Use whichever matches the repository you are pulling from. Mixing them up fails with - `Could not find`, which reads like a missing library rather than a wrong spelling. + Use the coordinate for the repository you are resolving from. ## Checking it worked -If you copied the starter folder, the example tests are the check. If you wired it by hand, this tells -you your test sourceset runs at all: +If you copied the starter folder, confirm that both example tests ran and passed. If you wired the +build by hand, add the [first simulated test](first-test.md), then run: ```powershell ./gradlew :TeamCode:testDebugUnitTest ``` -A green run with zero tests means the wiring is right and you have not written one yet. That is exactly -where [Your first simulated test](first-test.md) picks up. +A successful task with zero tests does not verify the installation. Check the test report and test +source path; after adding the slide example, the report must show `SlideReachesTargetExampleTest`. +Open `TeamCode/build/reports/tests/testDebugUnitTest/index.html` for the per-test results. diff --git a/SimLoop/docs/index.md b/SimLoop/docs/index.md index c389020..749686b 100644 --- a/SimLoop/docs/index.md +++ b/SimLoop/docs/index.md @@ -57,13 +57,12 @@ that does not exist. [What it does not do](limits.md) is the page that matters m ## The idea in one paragraph -A real robot loop reads hardware, decides, and writes hardware, fifty times a second. SimLoop replaces -only the hardware: `FakeMotor` really is a `DcMotorEx`, `FakeHardwareMap` really is a `HardwareMap`, so -your code cannot tell the difference and does not have to be modified to be tested. Behind each fake sits -a **plant** — a small, parameterized model of how that piece of the robot actually moves. Your season's -numbers reach those plants through the `config` interfaces, which is the one-way seam: SimLoop never -imports your package. A run is driven by `ScenarioRunner`, which owns the clock and writes the log, and -the log is what you assert on afterwards. +A robot loop reads hardware, decides, and writes hardware. SimLoop supplies SDK-compatible fakes: +`FakeMotor` implements `DcMotorEx`, and `FakeHardwareMap` extends `HardwareMap`. Robot code using the +supported methods can run against those devices. **Plants** model mechanism and drivetrain motion; +your test wires their state into sensors and supplies your robot's parameters through `config` +interfaces. SimLoop never imports your team's package. `ScenarioRunner` owns the fixed clock and +logging session, and your test asserts on the resulting log. ## What is in it diff --git a/SimLoop/docs/llms.md b/SimLoop/docs/llms.md index 994d710..8065c74 100644 --- a/SimLoop/docs/llms.md +++ b/SimLoop/docs/llms.md @@ -33,7 +33,7 @@ reference before calling them; the published alpha coordinate above does not con | [Packages overview](packages/index.md) | Which package does what and how they connect. | Choosing where a thing lives. | | [`fakehardware`](packages/fakehardware.md) | Fake SDK 12.0 devices and `FakeHardwareMap`: motors with the hub's closed loops, servos, IMU, Pinpoint, OTOS, Limelight 3A, touch, distance and colour sensors. Ticks, volts, `[-1, 1]` power; millimetres and radians on the odometry seam. | Wiring hardware into a test. | | [`plant`](packages/plant.md) | First-order mechanism and drivetrain models: time constant, max speed, end stops, gravity fraction. Drivetrain pose is in the **localizer frame**: +x forward, +y left, CCW-positive. | Modelling motion, or explaining a response. | -| [`config`](packages/config.md) | The interfaces the season implements. Full unit table per method. The two defaults that quietly lie. | Writing a robot's sim config. | +| [`config`](packages/config.md) | Configuration interfaces and units; strafe-speed and mounting-sign defaults to check against the robot. | Writing a robot's sim config. | | [`sim`](packages/sim.md) | `FakeTimer`, `ScenarioRunner`, `RlogDecodedCompare`. The four reasons a run refuses to return a log. | Running a scenario, or diagnosing a thrown run. | | [`opmode`](packages/opmode.md) | `OpModeRunner`: an unchanged `OpMode` or `LinearOpMode` through INIT, the loop and STOP, with scripted gamepads and recorded telemetry. | Testing an OpMode as written. | | [`simclock`](packages/simclock.md) | How `OpModeRunner.forTeamClass` moves a team's own `ElapsedTime`, `sleep` and `System.nanoTime()` onto sim time, and what it cannot move. | Timer code that reads the real clock. | diff --git a/SimLoop/docs/packages/config.md b/SimLoop/docs/packages/config.md index 4257803..ae7338b 100644 --- a/SimLoop/docs/packages/config.md +++ b/SimLoop/docs/packages/config.md @@ -36,7 +36,7 @@ names. SimLoop reads that configuration without importing your robot or season c | `timeConstant()` | seconds, strictly positive | | `maxSpeed()` | your position units per second, strictly positive | | `minPosition()`, `maxPosition()` | your position units | -| `gravityHoldPowerFraction()` | unitless, `[0, 1)`, always **positive** | +| `gravityHoldPowerFraction()` | unitless, `[0, 1)`; `0` means no gravity load | `PositionalServoSimConfig` @@ -45,12 +45,11 @@ names. SimLoop reads that configuration without importing your robot or season c | `positionAtCommandZero()`, `positionAtCommandOne()` | your position units | | `commandDeadband()`, `commandQuantum()` | servo command units, the same `[0, 1]` scale as the command | -!!! danger "Two of these have defaults that quietly lie" +!!! warning "Set strafe speed and wheel mounting signs deliberately" `maxLateralVelocityTicksPerSecond()` and `wheelMountingSigns()` have defaults, so a config that - ignores them compiles. A wrong mounting sign makes a robot spin instead of drive; a wrong strafe speed - makes it out-run its own path follower sideways — both in simulation only, so you debug a control - problem that is really a config problem. Answer both deliberately. "We measured it and there is no - penalty" is a fine answer; "we never looked" is not. + omits them compiles. A wrong mounting sign can make the simulated robot spin instead of drive; + a wrong strafe speed changes its response to the path follower. Check these values against your + robot before tuning the controller from simulation results. ## Positional servos: why two separate numbers diff --git a/SimLoop/docs/packages/fakehardware.md b/SimLoop/docs/packages/fakehardware.md index a3b7752..bfabc0d 100644 --- a/SimLoop/docs/packages/fakehardware.md +++ b/SimLoop/docs/packages/fakehardware.md @@ -2,9 +2,9 @@ `org.horizon36596.simloop.fakehardware` -Fake devices that implement the **real** FTC SDK 12.0 interfaces. Your robot code resolves them from a -`FakeHardwareMap` by the same config name it uses on the robot, and cannot tell the difference. That is -why your code does not have to be modified to be tested. +Fake devices implement FTC SDK 12.0 interfaces. Your robot code resolves them from a +`FakeHardwareMap` by the same config name it uses on the robot. Code that uses the supported SDK +methods can run unchanged; the models and unsupported calls are described below. ## What you use @@ -160,8 +160,9 @@ counts for a game piece must be measured on the real sensor. ## Two things worth knowing -**State changes only in `update`.** Every getter is a pure read. That is what makes a tick reproducible: -nothing moves because you looked at it. +**Getters do not advance the model.** Motion and source-driven sensor readings advance in `update`. +Commands and scripted setters can change state immediately: for example, `setPressed(true)` changes +the touch reading without waiting for another tick. **Sim-only accessors are marked as such.** Methods like `FakeMotor.getPhysicalPositionTicks()` and `FakeCRServo.getPhysicalPower()` report the simulation's truth rather than what the device would report. @@ -169,9 +170,9 @@ They are useful in a test and do not exist on real hardware, so never wire seaso ## What this package will not do -- **It will not move a mechanism.** A fake on its own only remembers what it was told. The motion is in - [`plant`](plant.md). A `FakeMotor` in open loop with no plant behind it reports a position that never - changes. +- **A motor shaft is not a whole mechanism.** `FakeMotor` integrates its own shaft speed and encoder + position when updated, even without a plant. Use [`plant`](plant.md) to model a mechanism's motion, + geometry and load rather than treating the unloaded shaft as the mechanism. - **No generic I2C peripherals.** `FakeI2cDeviceSynchSimple` models the bus *configuration* a driver's constructor performs. Every read and write **throws**, on purpose: there is no peripheral behind the bus. The Pinpoint and OTOS fakes emulate their own register files instead. diff --git a/SimLoop/docs/packages/plant.md b/SimLoop/docs/packages/plant.md index e35dcd3..c00137f 100644 --- a/SimLoop/docs/packages/plant.md +++ b/SimLoop/docs/packages/plant.md @@ -20,17 +20,16 @@ one tick, and that state is what the fake then reports back to your robot code. Each mechanism axis is **first-order**, with four parameters: -- a **time constant** (seconds) — a commanded step reaches ~63% of the way in one, and settles in four or - five; -- a **max speed** (position units per second) — the effort ceiling, which saturates large steps so the - response ramps instead of jumping; +- a **time constant** (seconds) — after a constant-power change, velocity closes about 63% of the gap + to its new target in one time constant, and about 98% in four. This is a velocity response, not a + promise that the mechanism reaches a position target in that time; +- a **max speed** (position units per second) — the ceiling used to turn net power into target velocity; - **end stops** (min and max position) — the state can never integrate past them, under any command; - a **gravity hold fraction** (unitless, `[0, 1)`) — how much of full effort the mechanism's own weight - costs it just to stay put. Always a **positive** number: the plant subtracts it, so gravity always pulls + costs it just to stay put. It is **nonnegative**: the plant subtracts it, so a nonzero value pulls toward `minPosition`. A turret or a horizontal extension reports `0.0`. -That is the whole model. It is calibration-ready — every parameter is something you can measure on a real -mechanism — and it is deliberately not more than that. +Measure these parameters on your mechanism. The model omits the effects listed at the end of this page. ## Units and frames diff --git a/SimLoop/docs/tested.md b/SimLoop/docs/tested.md index 226ba20..575512a 100644 --- a/SimLoop/docs/tested.md +++ b/SimLoop/docs/tested.md @@ -12,8 +12,8 @@ If you try it, you are testing it. Reports help most where this page says "not y - **Your code runs unchanged.** One team's season code runs its OpModes, autos and TeleOp under the runner, along with the example robot in the starter folder. The library's own suite is about 1,200 tests. -- **The same run gives the same log.** Every run is repeated in the tests and the two logs are compared - field by field. +- **Replay determinism.** Replay tests repeat scenarios and compare decoded robot-authored fields. + This checks the scenarios covered by those tests, not every possible consuming robot program. - **The fakes match the FTC SDK.** Every fake device is checked against the SDK 12.0 class it stands in for. The Pinpoint and OTOS fakes run the SDK's own drivers on emulated registers. - **Installing from JitPack.** The starter folder was copied into one fresh FTC project, which resolved diff --git a/docs/documentation-review-2026-10-24.md b/docs/documentation-review-2026-10-24.md index f3db976..d6b9cee 100644 --- a/docs/documentation-review-2026-10-24.md +++ b/docs/documentation-review-2026-10-24.md @@ -30,10 +30,16 @@ that those classes resolve from the pinned alpha coordinate. its generated-reference link to work under the site's `/simloop/` path. - Added a documentation-build check that compares the complete slide tutorial with its compiled example, so changes cannot leave an older snippet behind unnoticed. +- Corrected hardware/model explanations: the fake motor advances its own shaft without a plant, + scripted sensor setters act immediately, the time constant describes velocity and gravity can be zero. +- Replaced the zero-test success claim with a test-report check, scoped variant/log ownership advice + to the starter, and removed stale no-tag and untested-JDK claims from the publishing guide. +- Corrected complete-site preview instructions: MkDocs alone does not serve the copied Javadoc. ## Verification -- `:SimLoop:docsSite --offline` passes, including strict MkDocs, version check and API doclint. +- `:SimLoop:docsSite --offline` passes with the pinned Material 9.7.7, including strict MkDocs, + version check, example check and API doclint. - The documented slide example passes: one test, no failures, errors or skips. - Its RLOG was decoded with `RlogTimeSeries`: 150 frames, final position 11.999820053 in, peak 13.150056536 in. These pass the tutorial's 0.25 in final tolerance and 14 in peak bound. @@ -43,6 +49,7 @@ that those classes resolve from the pinned alpha coordinate. targets. The API and slide-log pages were rendered in Chrome with no page overflow. Exact final link counts are recorded in the PR. - The tutorial source checker passes on the guide and fails on a copy with one changed tick count. +- Three existing tests pass for shaft integration, immediate touch readings and the 63% velocity response. - The repository leak gate passes all 10 rules, including the added plot and CSV. The plot describes simulated values from the example's placeholder model, not measured robot motion. @@ -62,3 +69,8 @@ Live retrieval on 2026-10-24 returned HTTP 404 from both was private at that check. The README no longer claims the site is live, and publishing instructions now check Pages settings, repository visibility, workflow status and the actual artifact before diagnosing a 404. This patch does not publish the site or change repository visibility. + +JitPack's alpha build-status endpoint returned `ok` for `9116406` with JDK 17, and its served POM +returned HTTP 200 on the same day. The POM lists `RobotCore`, `Hardware` and PsiKit `core` at compile +scope, with no PsiKit `ftc` dependency. Existing hosted metadata works even though the source was +private at this check; it does not establish that a future tag can build or that the docs are deployed. From 011a74ce0841dbf5cba91b3bff221dbfea34c6c0 Mon Sep 17 00:00:00 2001 From: Saket T Date: Sat, 24 Oct 2026 05:52:55 -0700 Subject: [PATCH 4/6] docs: fix narrow layouts and record full local validation --- SimLoop/docs/stylesheets/horizon.css | 4 ++++ docs/documentation-review-2026-10-24.md | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/SimLoop/docs/stylesheets/horizon.css b/SimLoop/docs/stylesheets/horizon.css index fe992a0..8acdbc2 100644 --- a/SimLoop/docs/stylesheets/horizon.css +++ b/SimLoop/docs/stylesheets/horizon.css @@ -523,9 +523,13 @@ pre, max-width: none; } +/* Keep article gutters when removing the desktop reading measure. Material's mobile code blocks + extend into them, so dropping the margins would also widen the page beyond the viewport. */ @media screen and (max-width: 76.1875em) { .md-content__inner { max-width: none; + margin-left: 0.8rem; + margin-right: 0.8rem; } } diff --git a/docs/documentation-review-2026-10-24.md b/docs/documentation-review-2026-10-24.md index d6b9cee..d22f123 100644 --- a/docs/documentation-review-2026-10-24.md +++ b/docs/documentation-review-2026-10-24.md @@ -35,6 +35,7 @@ that those classes resolve from the pinned alpha coordinate. - Replaced the zero-test success claim with a test-report check, scoped variant/log ownership advice to the starter, and removed stale no-tag and untested-JDK claims from the publishing guide. - Corrected complete-site preview instructions: MkDocs alone does not serve the copied Javadoc. +- Restored narrow-layout article gutters so code blocks no longer widen the page beyond the viewport. ## Verification @@ -50,7 +51,11 @@ that those classes resolve from the pinned alpha coordinate. link counts are recorded in the PR. - The tutorial source checker passes on the guide and fails on a copy with one changed tick count. - Three existing tests pass for shaft integration, immediate touch readings and the 63% velocity response. +- Full local `test :SimLoop:docsSite --offline` passes: 1,249 library, six desktop and two starter + example tests, with no failures, errors or skips. - The repository leak gate passes all 10 rules, including the added plot and CSV. +- Responsive checks across both sites pass on all 55 guide pages at 320, 390 and 768 px with no + page-width overflow; wide tables and code can still scroll within their own containers. The plot describes simulated values from the example's placeholder model, not measured robot motion. The OpenCV fragment requires the consuming team's processor and native library; it is not a standalone @@ -74,3 +79,6 @@ JitPack's alpha build-status endpoint returned `ok` for `9116406` with JDK 17, a returned HTTP 200 on the same day. The POM lists `RobotCore`, `Hardware` and PsiKit `core` at compile scope, with no PsiKit `ftc` dependency. Existing hosted metadata works even though the source was private at this check; it does not establish that a future tag can build or that the docs are deployed. + +Hosted CI did not start: runs `40095139214` and `40095139201` report an account payment/spending-limit +restriction on every job, before any build or test step. Local results above are not a hosted CI pass. From df9e2d4146410087b84d8589e6e9170000d0d1c3 Mon Sep 17 00:00:00 2001 From: Saket T Date: Sat, 24 Oct 2026 11:29:09 -0700 Subject: [PATCH 5/6] docs: state AdvantageScope-only release interface --- README.md | 4 ++++ SimLoop/docs/index.md | 6 ++++++ docs/documentation-review-2026-10-24.md | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/README.md b/README.md index 71d6a69..b2f1a5e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ score automatically. Package root: `org.horizon36596.simloop`. Licence: BSD 3-Clause. +Run SimLoop from your robot project's tests or simulation harness and view the result in a separate +AdvantageScope window. This release covers that library-and-viewer workflow. The standalone app +being built around SimLoop is not ready and is not part of this release. + The source tree can be newer than the latest tag. Distortion, occlusion, tilted tags and synthetic camera frames are development additions after `v0.2.0-alpha.1`; they are not in that pinned artifact. Use the starter and documentation from the tag you install, or pin the exact source commit you test. diff --git a/SimLoop/docs/index.md b/SimLoop/docs/index.md index 749686b..042fb6a 100644 --- a/SimLoop/docs/index.md +++ b/SimLoop/docs/index.md @@ -9,6 +9,12 @@ score automatically. No robot, no Driver Station, no emulator. A test run is a few seconds. +## What you open + +Run SimLoop from your robot project's tests or simulation harness. **AdvantageScope opens in its +own window** to display the simulation's logs and visualizations. The standalone app being built +around SimLoop is not ready and is not included in this release. + !!! warning "Status: alpha" `v0.2.0-alpha.1` is the current release and the first public alpha. The API may change between alpha versions, so **pin the exact version** in your build. It needs **FTC SDK 12.0**. Questions go diff --git a/docs/documentation-review-2026-10-24.md b/docs/documentation-review-2026-10-24.md index d22f123..5d12614 100644 --- a/docs/documentation-review-2026-10-24.md +++ b/docs/documentation-review-2026-10-24.md @@ -13,6 +13,10 @@ that those classes resolve from the pinned alpha coordinate. ## Changes made +The owner clarified the release UI: SimLoop uses a separate AdvantageScope window. The surrounding +standalone app is not release-ready and is not being shipped. The README and site introduction now +state that scope. This review added a plot of logged values, not a screenshot or mockup of that app. + - Marked the source/tag boundary on the home page, feature list, vision page, agent index and README. - Linked installation to the starter at the pinned tag, added the Android SDK prerequisite and explained first-run dependency downloads. From 64ad4eea3a09a95b27a545a581164db1d6dc06ec Mon Sep 17 00:00:00 2001 From: Saket T Date: Sat, 24 Oct 2026 13:32:32 -0700 Subject: [PATCH 6/6] docs: drop dated visibility and CI notes, and the unannounced app line The review note and PUBLISHING.md recorded the repository's private status, docs 404s and hosted-CI billing on 2026-10-24. That goes stale and would become public with the repo. Also: the user opens the log in AdvantageScope (it does not open on its own), and the root holds the desktop module and examples beside the library. --- README.md | 3 +- SimLoop/PUBLISHING.md | 16 ++--- SimLoop/docs/api-reference.md | 2 +- SimLoop/docs/index.md | 5 +- docs/documentation-review-2026-10-24.md | 88 ------------------------- 5 files changed, 10 insertions(+), 104 deletions(-) delete mode 100644 docs/documentation-review-2026-10-24.md diff --git a/README.md b/README.md index b2f1a5e..649ea58 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,7 @@ score automatically. Package root: `org.horizon36596.simloop`. Licence: BSD 3-Clause. Run SimLoop from your robot project's tests or simulation harness and view the result in a separate -AdvantageScope window. This release covers that library-and-viewer workflow. The standalone app -being built around SimLoop is not ready and is not part of this release. +AdvantageScope window. This release covers that library-and-viewer workflow. The source tree can be newer than the latest tag. Distortion, occlusion, tilted tags and synthetic camera frames are development additions after `v0.2.0-alpha.1`; they are not in that pinned artifact. diff --git a/SimLoop/PUBLISHING.md b/SimLoop/PUBLISHING.md index 1d02987..5b78b20 100644 --- a/SimLoop/PUBLISHING.md +++ b/SimLoop/PUBLISHING.md @@ -83,9 +83,8 @@ to older tags where they explain compatibility or historical checks. - **Whether JitPack's environment builds the tag.** `jitpack.yml` pins `openjdk17`. Check the new build's log rather than assuming a local build proves the hosted environment works. - Check repository visibility and JitPack access before relying on a new hosted build. GitHub reported - this repository as private on 2026-10-24; its historical public setting is not its current state. - The owner decides any visibility or hosting change separately. + Check repository visibility and JitPack access before relying on a new hosted build. The owner + decides any visibility or hosting change separately. 6. **Tell the teams the coordinate and the two repository lines**, not just the coordinate. The PsiKit one is the thing that bites — see below. @@ -194,9 +193,8 @@ tag existed — which also moved the published coordinate to `com.github.Horizon JitPack takes the artifact id straight from the repository name. The landing repository was created on 2026-09-19. Check the current DNS, Pages settings and latest -deployment before repeating setup steps. On 2026-10-24 the custom domain answered, but `/simloop/` -and `/simloop/javadoc/` returned HTTP 404 while this repository was private. The source and a successful -local build do not establish that the project site is deployed. +deployment before repeating setup steps. A successful local build does not show that the project +site is deployed. **1. Create the landing repository.** ✅ **Done 2026-09-19** — `Horizon-36596/Horizon-36596.github.io`, public, with the landing page at its root and Pages serving `main` / `(root)`. The name is not a choice: @@ -344,11 +342,9 @@ file assumes. `v` is what declares a commit usable, and everything downstream reacts to it. No workflow creates a tag. This is the same rule root `CLAUDE.md` applies to push, PR and merge — the act that leaves the machine is a human's. -- **The original plan tied the documentation site to public source.** The repository was made public - on 2026-09-17, but GitHub reported it as private on 2026-10-24, when the docs routes returned 404. - Published tags now exist and the alpha's JitPack metadata is available. Check current access before +- **The original plan tied the documentation site to public source.** Published tags now exist and the alpha's JitPack metadata is available. Check current access before the next release; do not infer visibility, publication or artifact access from the original plan. -- **The library is a Gradle subproject of a repository that holds only it.** The reasoning is above, +- **The library is a Gradle subproject, not the root project.** The reasoning is above, under "Why the library is a subproject and not the root project". Publishing one module is what preserves the JitPack artifact name; the directory name alone does not determine it. - **Documentation is not versioned** until the library is past alpha. One site, no version selector, with diff --git a/SimLoop/docs/api-reference.md b/SimLoop/docs/api-reference.md index 65d784b..92d3ae5 100644 --- a/SimLoop/docs/api-reference.md +++ b/SimLoop/docs/api-reference.md @@ -23,7 +23,7 @@ Running `mkdocs serve` alone previews the guide but does not copy the API pages. run `:SimLoop:apiDocs` and open `SimLoop/build/docs/apiDocs/index.html`. (Run it from the repository root, not from inside `SimLoop/`. The library is a Gradle subproject of a -repository that holds only it, so the `:SimLoop` prefix is correct.) +repository that also holds its desktop module and the examples, so the `:SimLoop` prefix is correct.) ## How it is kept complete diff --git a/SimLoop/docs/index.md b/SimLoop/docs/index.md index 042fb6a..6fb2eee 100644 --- a/SimLoop/docs/index.md +++ b/SimLoop/docs/index.md @@ -11,9 +11,8 @@ No robot, no Driver Station, no emulator. A test run is a few seconds. ## What you open -Run SimLoop from your robot project's tests or simulation harness. **AdvantageScope opens in its -own window** to display the simulation's logs and visualizations. The standalone app being built -around SimLoop is not ready and is not included in this release. +Run SimLoop from your robot project's tests or simulation harness, then **open the log it writes in +AdvantageScope**, in its own window, to see the run. !!! warning "Status: alpha" `v0.2.0-alpha.1` is the current release and the first public alpha. The API may change between diff --git a/docs/documentation-review-2026-10-24.md b/docs/documentation-review-2026-10-24.md deleted file mode 100644 index 5d12614..0000000 --- a/docs/documentation-review-2026-10-24.md +++ /dev/null @@ -1,88 +0,0 @@ -# Documentation review, 2026-10-24 - -## Source checked - -GitHub was checked before editing. `main` was `7e06448`; the queued work was vision #24, streaming -RLOG reads #21, scenario cleanup #22, docs analytics #23 and branding #25. The integration comment on -#24 records a combined local run at `eb91fad`. It does not confirm a publication time. - -This documentation branch is based on `feat/vision-fidelity` at `4b40ef9`. It supplements the vision -documentation already in #24. The `v0.2.0-alpha.1` tag resolves to `9116406` and does not contain the -new distortion, occlusion, tilted-tag or synthetic-frame classes. Installation docs must not imply -that those classes resolve from the pinned alpha coordinate. - -## Changes made - -The owner clarified the release UI: SimLoop uses a separate AdvantageScope window. The surrounding -standalone app is not release-ready and is not being shipped. The README and site introduction now -state that scope. This review added a plot of logged values, not a screenshot or mockup of that app. - -- Marked the source/tag boundary on the home page, feature list, vision page, agent index and README. -- Linked installation to the starter at the pinned tag, added the Android SDK prerequisite and - explained first-run dependency downloads. -- Clarified that the installed starter tests and the slide tutorial are different examples. -- Added vision output selection, channel order, native OpenCV setup, processor initialization, - calibration requirements, Mat cleanup and deterministic frame-sequence guidance. -- Explained projection height, robot/field coordinates, empty sightings and a missing plane intersection. -- Added logging ownership and corrected the distinction between model time and I/O waits. -- Expanded nondeterminism troubleshooting beyond wall-clock reads. -- Replaced broad claims about automatic tool support and repeated agent advice with explicit setup and - review steps. Added the missing pointers README and replaced unsupported cost-saving percentages - and benchmark claims with a usage-measurement workflow. -- Added a plot of a real run of the documented slide test, its exported values and regeneration tools. -- Moved the API landing page to `api-reference/` so the Javadoc copy cannot overwrite it, and fixed - its generated-reference link to work under the site's `/simloop/` path. -- Added a documentation-build check that compares the complete slide tutorial with its compiled - example, so changes cannot leave an older snippet behind unnoticed. -- Corrected hardware/model explanations: the fake motor advances its own shaft without a plant, - scripted sensor setters act immediately, the time constant describes velocity and gravity can be zero. -- Replaced the zero-test success claim with a test-report check, scoped variant/log ownership advice - to the starter, and removed stale no-tag and untested-JDK claims from the publishing guide. -- Corrected complete-site preview instructions: MkDocs alone does not serve the copied Javadoc. -- Restored narrow-layout article gutters so code blocks no longer widen the page beyond the viewport. - -## Verification - -- `:SimLoop:docsSite --offline` passes with the pinned Material 9.7.7, including strict MkDocs, - version check, example check and API doclint. -- The documented slide example passes: one test, no failures, errors or skips. -- Its RLOG was decoded with `RlogTimeSeries`: 150 frames, final position 11.999820053 in, peak - 13.150056536 in. These pass the tutorial's 0.25 in final tolerance and 14 in peak bound. -- The tutorial's executable Java matches the maintained test source; the only omitted block is the - class-level documentation comment. -- Local file, image and anchor crawl passes across the rendered guide and its generated-reference - targets. The API and slide-log pages were rendered in Chrome with no page overflow. Exact final - link counts are recorded in the PR. -- The tutorial source checker passes on the guide and fails on a copy with one changed tick count. -- Three existing tests pass for shaft integration, immediate touch readings and the 63% velocity response. -- Full local `test :SimLoop:docsSite --offline` passes: 1,249 library, six desktop and two starter - example tests, with no failures, errors or skips. -- The repository leak gate passes all 10 rules, including the added plot and CSV. -- Responsive checks across both sites pass on all 55 guide pages at 320, 390 and 768 px with no - page-width overflow; wide tables and code can still scroll within their own containers. - -The plot describes simulated values from the example's placeholder model, not measured robot motion. -The OpenCV fragment requires the consuming team's processor and native library; it is not a standalone -test, and no end-to-end OpenCV execution is claimed for this patch. - -## Pending work already covered elsewhere - -#21 adds the long-log streaming documentation. #22 owns the logger-reset fix. #23 owns the counter and -privacy page; #25 owns the logo assets. Keep their documentation with their code when merging. A -published tag containing #24 must be chosen separately; this patch changes no version or release. - -## Published site check - -Live retrieval on 2026-10-24 returned HTTP 404 from both -`https://libraries.horizon36596.org/simloop/` and its `/javadoc/` route. The connected GitHub repository -was private at that check. The README no longer claims the site is live, and publishing instructions -now check Pages settings, repository visibility, workflow status and the actual artifact before -diagnosing a 404. This patch does not publish the site or change repository visibility. - -JitPack's alpha build-status endpoint returned `ok` for `9116406` with JDK 17, and its served POM -returned HTTP 200 on the same day. The POM lists `RobotCore`, `Hardware` and PsiKit `core` at compile -scope, with no PsiKit `ftc` dependency. Existing hosted metadata works even though the source was -private at this check; it does not establish that a future tag can build or that the docs are deployed. - -Hosted CI did not start: runs `40095139214` and `40095139201` report an account payment/spending-limit -restriction on every job, before any build or test step. Local results above are not a hosted CI pass.