Fixes from the 2026-10-13 review: metrics, General menu, event loop, code health #1

Manually merged
vinny merged 0 commits from vinny/improvements into master 2026-10-13 15:46:17 -04:00 AGit
Owner

Fixes and code improvements from a review of CMOnet on 2026-10-13: the live controller's metrics, the General's tool menu, event-loop blocking, and code health.

Base note: local master is 24 commits ahead of origin/master (a4d92cb). This branch builds on local master, so the PR also carries those commits (eef6662 and earlier, already committed by the operator). The commits that belong to this PR are the last three.

Metrics and menu (51d26af)

  • LLM latency was blank for the Scenario Builder. Every model request now records submitted_at when it starts running (agents/queue.py). All 3,794 requests in the operator's store were builder calls, and none were timed. Flows also record duration_seconds.
  • Latency is reported per role. The builder's long thinking calls no longer hide the General's turns.
  • Corrected the provider-queue note. It still said unknown requests "keep their slot until an operator confirms the provider idle", which contradicts the owner decision of 2026-10-03.
  • Unresolved actions are split.
    • unresolved_blocking and unresolved_archived_expired are reported separately, and there is a Prometheus gauge.
    • The live store holds 114 outcome_unknown actions from 20–26 September. They no longer block anything and can never be read back.
  • The General is no longer offered tools that will be refused. With hierarchy management off (the default), the menu no longer offers operations.revise or resource reservations.
    • execute() refused those calls, and the live order tests c and m hit this.
    • The initiative rule no longer tells the General to use them.
    • The menu and the gate now share requires_hierarchy().

Event loop, world delivery metric and General guidance (d7ce74c)

  • Database reads moved off the event loop.
    • The agent scheduler's per-pass gate (4×/s) and the read-back monitor's next-verification read (2×/s) now read SQLite in a worker thread.
    • World packets are JSON-encoded in a worker thread.
  • The world-stream delivery metric measured evidence age, not delivery. It showed p95 152 s live on the 1,435-unit Iran side.
    • Each world change's write time is now kept in memory (bounded).
    • Each packet carries written_at, and the probe times frames from that.
    • The four world_changes inserts now share one helper.
  • General guidance.
    • The grouped-movement guard names groups.remove_units when the menu offers it.
    • doctrine.set_value guidance tells the model to check player_editable first. A real model set a locked weapons-control status twice per run.
  • Command center.
    • Bursts of journal entries refresh status at most once a second.
    • api() reports an HTML or text error page as an ApiError instead of a JSON SyntaxError.
    • Shipped JavaScript rebuilt.

Code health (0289dab)

  • Removed unused code.
    • domain/ports.py
    • Seven functions nothing calls, in builder placement/roles, geo, Lua Bench, and planning fuel/geodesy/packages, plus imports and a constant left unused by them.
  • One great-circle distance. Six copies (operational tools, picture, controller, mobilize, agent events, supervisor predicates) now use planning.geodesy.distance_nm/bearing_deg. The tester's byte copies and the builder's geo module are unchanged.
  • Builder database hardening. The builder opens the uploaded CMO database with trusted_schema=OFF, query_only and no attached databases, as the catalog service does.
  • Configuration examples.
    • config/example.settings.json lists all 63 settings. It was missing 13, and a test now requires every field, in order.
    • config/environment.example documents the TLS, provider CA bundle, proxy token, builder database/import folder and watchdog variables. A test checks that it names every variable the code reads.
  • Docs.
    • RUNTIME_HARDENING.md: the new metric fields and the 30 s busy timeout.
    • AI_AGENT_TOOL_CATALOG.md: no longer claims the game writes are fail-closed in controlled mode.

Verification

python tools/check.py (native Lua 5.4) on the branch:

  • contracts, registry (238 definitions) and evidence documents match
  • pytest: 2,210 passed and 3 skipped, with 1 failure, a doc-count test that my catalog-doc wording broke. That wording was fixed in 0289dab and the test file re-run (95 passed). Evidence, registry and contract checks were then re-run, all clean.
  • python -m cmonet --check passes
  • the strict TypeScript build matches the shipped JavaScript

The baseline before these changes was 2,209 passed and 3 skipped.

No CMO actions were taken. The live controller was touched only by read-only API queries and a read-only SQLite connection.

Not in this PR

The larger items are filed as issues:

  • #2 the General never uses the deterministic planner
  • #3 a 1,400-unit side is mostly invisible to the General
  • #4 every turn re-sends the whole packet
  • #5 missing player functions (flight plans/TOT, refuel from a tanker mission, cargo)
  • #6 library writes cannot be live-verified
  • #7 build the laboratory corpus with the Scenario Builder
  • #8 imported flows: live verification (FL-8)
  • #9 archived outcome_unknown actions kept forever
  • #10 version labels stuck at 0.6.3 / Stage 6
  • #11 legacy GeneralWorker in stage 3 tests
  • #12 split the largest modules; coordinator SQLite on the event loop
  • #13 no CI runner; serial tests, no lint

🤖 Generated with Claude Code

Fixes and code improvements from a review of CMOnet on 2026-10-13: the live controller's metrics, the General's tool menu, event-loop blocking, and code health. **Base note:** local `master` is 24 commits ahead of `origin/master` (a4d92cb). This branch builds on local master, so the PR also carries those commits (eef6662 and earlier, already committed by the operator). The commits that belong to this PR are the last three. ## Metrics and menu (51d26af) - **LLM latency was blank for the Scenario Builder.** Every model request now records `submitted_at` when it starts running (`agents/queue.py`). All 3,794 requests in the operator's store were builder calls, and none were timed. Flows also record `duration_seconds`. - **Latency is reported per role.** The builder's long thinking calls no longer hide the General's turns. - **Corrected the provider-queue note.** It still said unknown requests "keep their slot until an operator confirms the provider idle", which contradicts the owner decision of 2026-10-03. - **Unresolved actions are split.** - `unresolved_blocking` and `unresolved_archived_expired` are reported separately, and there is a Prometheus gauge. - The live store holds 114 `outcome_unknown` actions from 20–26 September. They no longer block anything and can never be read back. - **The General is no longer offered tools that will be refused.** With hierarchy management off (the default), the menu no longer offers `operations.revise` or resource reservations. - `execute()` refused those calls, and the live order tests c and m hit this. - The initiative rule no longer tells the General to use them. - The menu and the gate now share `requires_hierarchy()`. ## Event loop, world delivery metric and General guidance (d7ce74c) - **Database reads moved off the event loop.** - The agent scheduler's per-pass gate (4×/s) and the read-back monitor's next-verification read (2×/s) now read SQLite in a worker thread. - World packets are JSON-encoded in a worker thread. - **The world-stream delivery metric measured evidence age, not delivery.** It showed p95 152 s live on the 1,435-unit Iran side. - Each world change's write time is now kept in memory (bounded). - Each packet carries `written_at`, and the probe times frames from that. - The four `world_changes` inserts now share one helper. - **General guidance.** - The grouped-movement guard names `groups.remove_units` when the menu offers it. - `doctrine.set_value` guidance tells the model to check `player_editable` first. A real model set a locked weapons-control status twice per run. - **Command center.** - Bursts of journal entries refresh status at most once a second. - `api()` reports an HTML or text error page as an `ApiError` instead of a JSON `SyntaxError`. - Shipped JavaScript rebuilt. ## Code health (0289dab) - **Removed unused code.** - `domain/ports.py` - Seven functions nothing calls, in builder placement/roles, geo, Lua Bench, and planning fuel/geodesy/packages, plus imports and a constant left unused by them. - **One great-circle distance.** Six copies (operational tools, picture, controller, mobilize, agent events, supervisor predicates) now use `planning.geodesy.distance_nm`/`bearing_deg`. The tester's byte copies and the builder's `geo` module are unchanged. - **Builder database hardening.** The builder opens the uploaded CMO database with `trusted_schema=OFF`, `query_only` and no attached databases, as the catalog service does. - **Configuration examples.** - `config/example.settings.json` lists all 63 settings. It was missing 13, and a test now requires every field, in order. - `config/environment.example` documents the TLS, provider CA bundle, proxy token, builder database/import folder and watchdog variables. A test checks that it names every variable the code reads. - **Docs.** - `RUNTIME_HARDENING.md`: the new metric fields and the 30 s busy timeout. - `AI_AGENT_TOOL_CATALOG.md`: no longer claims the game writes are fail-closed in controlled mode. ## Verification `python tools/check.py` (native Lua 5.4) on the branch: - contracts, registry (238 definitions) and evidence documents match - pytest: 2,210 passed and 3 skipped, with 1 failure, a doc-count test that my catalog-doc wording broke. That wording was fixed in 0289dab and the test file re-run (95 passed). Evidence, registry and contract checks were then re-run, all clean. - `python -m cmonet --check` passes - the strict TypeScript build matches the shipped JavaScript The baseline before these changes was 2,209 passed and 3 skipped. No CMO actions were taken. The live controller was touched only by read-only API queries and a read-only SQLite connection. ## Not in this PR The larger items are filed as issues: - #2 the General never uses the deterministic planner - #3 a 1,400-unit side is mostly invisible to the General - #4 every turn re-sends the whole packet - #5 missing player functions (flight plans/TOT, refuel from a tanker mission, cargo) - #6 library writes cannot be live-verified - #7 build the laboratory corpus with the Scenario Builder - #8 imported flows: live verification (FL-8) - #9 archived outcome_unknown actions kept forever - #10 version labels stuck at 0.6.3 / Stage 6 - #11 legacy GeneralWorker in stage 3 tests - #12 split the largest modules; coordinator SQLite on the event loop - #13 no CI runner; serial tests, no lint 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Live 2026-10-10 "an airbase at Diego Garcia, protected by anti air, with
tankers, 24 F-22s and bombers" in a drawn Indian Ocean area put the bombers
at Thumrait in Oman, outside the area, gave the F-22s the side's F-15EX, and
placed the tankers, fighters and their SAMs nowhere; each retry added more.

- The change patch takes a base (`based_at`), a named type per role
  (`class`) and a side. A named airfield becomes the side's base, its land a
  host nation when no hostile side holds it; an operating area that is an
  airfield bases a new package's aircraft too.
- Bases chosen in code stay inside the package's strict drawn area.
- A named type is looked up among the role's classes ("B-2" is the B-2A,
  not the B-21) instead of the side's class chosen before.
- SAM, gun and ground units in an aircraft package become a package of
  their own at its base; a model's "air base" kind is never code's.
- An area's change includes its packages placed nowhere or based outside
  it, so a request can replace an earlier one's; bases left empty go.
- Sites keep 0.3 nm (not 1.5) off base structures and search a small
  island's land closer, so Diego Garcia holds its air defence.
- The page lists the packages a change could not place in full.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live 2026-10-10 a request for Area 16, drawn after drafting, was refused:
a change needed a selected package to take its side and theatre from.
Now an area picked with nothing in it takes new packages: the model sees
its countries, seas and airfields and names each package's side, and the
area becomes a drafted theatre of its own. An operating area within 30 nm
of an airfield bases a new package's aircraft there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live 2026-10-10 a spawn listed 30 merchant ships 0 to 11 nm off their
countries' coasts as outside their strict country areas, 24 F-22s at Diego
Garcia stationed at the Indian Ocean's centre 1,000 nm off, and four
destroyers of a class the database has one hull of.

- Gate 15: a ship of a country's area in its territorial sea is inside it.
- A change request's squadron whose station is beyond its reach even with
  tankers holds its station over its base, noted.
- Ship classes with hulls enough for the count are offered first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live 2026-10-10 two merchant ships stood in the desert, in gaps where Saudi
Arabia's outline meets Iraq's and the UAE's, which read as sea; CMO would
refuse them. A point on no country's land is now at sea only with open
water 2 nm off on some bearing (the Bosphorus and the Venice lagoon stay
sea), and gate 15 counts a country area's ship in its waters only at sea.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live 2026-10-10 CMO added no SURC to a Ticonderoga's davit, which the
database says fits it, and the spawn stopped with most of the plan unsent.
A docked boat CMO adds nothing for (its GUID read as absent by the tool) is
recorded as not added, noted in the receipt, and the spawn goes on; nothing
is sent twice. Any other unit CMO refuses still stops the spawn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live 2026-10-10 a spawn left out 14 Small Unit Riverine Craft: the database
sizes the 12 m SURC for the 0-11 m davits of Ticonderogas and Burkes, and
CMO, which goes by length, took none aboard; 10.7 m RHIBs and GARCs on the
same davits went in.

- A boat fits a dock or davit only within its size's length (11, 17, 25,
  45 m), when its class is chosen and when it is docked.
- Gate 8 names a boat longer than its ship's davits or docks take, for
  plans drafted before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the operator's game (Iran vs WORLD, side Iran: 1,435 own units, 3,522
contacts) collection kept pausing with "contacts lost its validated response
after submission (proxy_error)". Collector 4.1.13 read every contact in one
request: 3 MB in 4.3-5.8 s, and CMO's socket stops a command after 5.0 s of
Lua execution ("elapsed-time budget exceeded"). Each automatic resume read
them again and hit the limit again, holding the game thread about 5 s a time.

Collector 4.1.14:
- Contacts come a page at a time, in GUID order, from side.contacts (37 ms
  for 3,522 plain {guid,name} tables; ScenEdit_GetContacts builds a wrapper
  per contact first, 1.4 s), each fetched by GUID with ScenEdit_GetContact.
  A page of 256 costs 0.37 s of game time (proxy HEALTH). ScenEdit_GetContacts
  stays the fallback when the side's list can't be read; a listed contact the
  getter doesn't return is reported, never taken as gone.
- The units collector (when a unit is firing) and the missions collector
  scope contact links with side.contacts too: missions went from 2.35 s to
  87 ms of game time.
- Contact pages adapt like own-unit batches but under their own ceiling, not
  the own-unit one, and a slow round trip stops halving them at 32.
- One cycle of pages completes the contact baseline although contacts come
  and go between pages; the last page's list decides which are gone. The
  priority burst covers the whole cycle and starts on a known contact's
  posture or classification change or a new hostile one, not on churn alone.
- The proxy's exact budget-abort reply is proxy_budget_exceeded. CMO stopped
  that script, so a read of several records is followed by one half its
  size instead of a pause; a second stop in a row, or a read that can't be
  smaller, pauses as before. Writes are never treated this way.
- ScenEdit_GetContact joins the adapters' reviewed reads (it was reviewed for
  flow bundles); the G2 goldens are regenerated for it.

Live measurements: docs/verification/LARGE-SCENARIO-CONTACTS-2026-10-11.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deployed on the operator's game (side Iran, 3,522 contacts), a contact cycle
took 14 pages and about 20 s, and the next one began 5 s after it ended:
contacts took two of every three reads by time, and the first sweep of the
1,435 own units reached 320 of them in 66 s, so readiness and configuration
could not start before the first-picture wait ran out.

A contact cycle now rests as long as it ran before the next one, so contacts
keep about half the reads and the own force's sweep and enrichment the rest.
A one-page cycle (a side with up to 256 contacts) keeps its cadence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UI
- The sidebar's 19 entries are grouped (Operate, AI, Game link, Reference), each with its
  own icon; Map Cache and Map Provider were one page under two entries and are now Maps
  (#providers still opens it). Attention Center and LLM Queue carry a count badge.
- Settings, tool and record pages (Settings, Diagnostics, Jobs, Event Bridge, Builder,
  Catalog, Command Audit, Maps, Profiles, Flows, LLM Queue, Rules, Replay) take the
  inspector's column and the feed's row; operational pages keep both.
- The bottom feed folds to its tab row; each browser remembers it. Scrolling back to the
  newest entry resumes auto-scroll.
- Status bar: ages read as "6 h 20 min" instead of seconds, dots show socket and
  observation state, the LLM cell names the General's state, nothing is cut off.
  The redundant stage marker is gone.
- Feed rows no longer call current records "archive" when the timeline arrived after the
  first rows, and "General (general)" reads "General (AI)".
- Command page shows Loading and keeps the mode control disabled until the real mode is
  read (it showed "Not configured" and Observation-only meanwhile).
- Replay lists only timelines that still hold records (the API says which); Maps cards,
  the replay form and audit filters are laid out properly; Attention Center page title
  matches its entry; ages in Jobs and LLM Queue are readable.
- Background tabs stop polling page data and poll status every 15 s.

Performance
- authorized_operations() read the observation context, control and evidence once per
  operation (~370 SQLite connections per /api/v1/status). It now takes one snapshot;
  a test holds it to authorization()'s answers. /api/v1/status: ~350 ms -> ~57 ms.
- The agent event tick parsed every own unit's document each second and
  hierarchy.ensure() JSON-scanned all entities in a write transaction each second;
  they now use the indexed kind column and read positions only when a Commander
  needs them. The policy collector skips considered events before reading contacts.
  Idle controller CPU on a copy of the live database: 33% -> 10% of a core.
- The development reloader polls source files once a second instead of four times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The inspector's CMO Missions and Attention Center panels fold to their headings; a
  selected unit then gets about twice the room. Each browser remembers the folds.
- The map roster has kind chips with counts (own forces, contacts, missions, reference
  points) over its ~5,000 alphabetical records.
- Settings folds the advanced socket/transport fields and the load-protection limits into
  groups; host and port stay in view, and a group opens if one of its fields is invalid.
- Staff Views are tiles instead of five full-width cards; "1 roles" reads "1 role".
- An inline favicon (the page asked for /favicon.ico on every load and got a 404).
- A composite whose steps were all refused for one reason now says it ("No
  dependency-plan primitive completed: A mission-level game action requires a current
  order..."), so a model reading the top line learns why (live 2026-10-11, navigation.go_to).
- README and the operator guide name the pages as the sidebar now does and describe the
  grouped sidebar, focus pages and folding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At CMO's main menu the probe returns no sides. CMOnet said 'Previously selected side is
absent. Select a side from the current scenario.' over an empty side list; it now says no
scenario is loaded and that it checks again every 10 seconds (it already re-probes then).
The status bar's socket cell shows amber with the reason on hover, not green, while such a
problem stands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Labels were drawn in the same pass as symbols, so a later symbol painted over an earlier
label, and room went to whichever record came first. Symbols are now drawn first, then
labels: the selected entity, hostile contacts, own units, unknown contacts, then the rest,
a label that would cover another still left out. The Flows run list shows start times to
the second and how long each run took.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each imported flow revision stored every one of its files again. On the operator's store
72 revisions of one flow held 29,124 file rows, 467 MB, of 815 distinct contents (52 MB),
44% of the database, and every new revision added about 7 MB. A content is now kept once
in flow_blobs by its SHA-256 and flow_files lists each revision's paths with it; an import
adds only what changed. The migration checks every stored file against its SHA-256 first
and stops with nothing changed on a mismatch.

Scenario Builder plan and order-of-battle versions keep whole documents of up to 3 MB
(264 MB for 89 versions there). Those of 16 KB or more are now stored packed, as tool
results are; BuilderStore reads either form and the migration checks each round-trip.

On a copy of the live store the upgrade took 12 s with its pre-upgrade backup; flow
exports and plan versions read back byte-for-byte as before; retention then returned the
freed space (1,018 MB -> 313 MB). Tests that relabel a current store as an older schema
drop schema 17's tables first (schema_fixture.remove_schema17_shape).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every page component fetched its data when the command center loaded, shown or not: 34 API
requests at once (Lua Bench lists, the builder's basemap, catalog, rules, incidents, replay
timelines...). With the browser's six connections per host, the status bar's own request
waited behind them and stayed 'Not initialized' for seconds. Pages already load on
cmo-page-visible; their unconditional first load now runs only when they are the page
shown (lib onScreen), the capability list and connection panel load when shown, and the
builder map fetches its geography when it first has a size. A first load is now 7
requests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Inspect on LLM Queue, a run on Flows and Inspect role on Hierarchy filled a panel below
long tables, out of sight, so nothing seemed to happen. The panel now scrolls into view
(and LLM Queue's folded detail opens).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first-picture wait counted from process start until a connection began, so a server
up for a while without connecting reported picture_ready with 'still incomplete after
the wait', and Settings showed 'awaiting roster · 0/0 own units ... picture ready'. Before
collection starts the picture is not ready and the note says to connect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- POLICY opens with the active orders as scoped (commit only what an order asks
  for; pursue the defeat of opposing forces when an order or campaign review is
  broad), and keeps the campaign active only for those. The packet already told a
  bounded order to commit only what it asks for (PROMPT_ANATOMY observation 4).
- The forces.mobilize / forces.strike_package preference moves from POLICY into
  their argument guidance, which the menu shows only when the tool is offered.
- The packet states the authorization rule once, without phrasing it against the
  removed per-side verification requirement.
- Defaults llm_max_output_tokens 8192 and llm_timeout_seconds 420, as measured for
  reasoning models (OPERATOR_GUIDE); contract schemas and example settings follow.
- AGENTS.md: the live policy-event collector exists; no adapter count; no stage in
  the title; packed tool results stated without dates.
- G2 builtin-general golden regenerated; diagram snapshot notes the policy changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DUkuwmTi6zGjP3ZWSNzjRd
pyproject's package-data left out web/dist/components/builder/*.js and
library/MANIFEST.json, library/tools/*.lua|*.json, which CMOnet reads at
runtime; tools/release.py builds from the same patterns. A test now checks
that every tracked non-Python file under src/cmonet is package data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
geo.py, gazetteer.py and basemap.py move to src/cmonet/geography: public
Natural Earth geography that both maps draw, so the Operations map can load
it without reaching the human-only Scenario Builder (the no-shortcuts audit
forbids non-builder code from importing cmonet.builder). The builder imports
them from there; /api/v1/builder/basemap is gone.

Level 0 also carries the seas' coarse outlines, for builder areas that are a
whole sea. The base map is revalidated by ETag instead of being forced
no-store, so a reload no longer downloads it again; tiles keep their own
day-long caching. Every other /api response stays no-store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
events.entity_id keeps the observation key (u:, c:, m: or a:) of the one
entity an event concerns, so the operations feed can show it on the map.
The observation events (contact first seen, mission changed, own unit
destroyed, damaged or entering an area) and every audited event that names
an entity (rules, incidents, agent and application actions) write it;
anything that is not an observation key is not kept. Older rows have none.

Agent context is unchanged: events.changes_since and the events.wait_for
evidence read the columns agents always saw. Test fixtures that relabel a
current store as an older schema drop the new column too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EntitySummary gains group_entity_id (an own unit's native group),
area_entity_ids (a mission's zone reference points) and assigned_entity_ids,
so the Operations map, which has only summaries, can collapse a facility
group into one base marker and draw mission areas.

The mission collector sorted a zone's reference points by GUID, which
crosses an outline drawn through them over itself; collector 4.1.15 keeps
CMO's order for zones (other id lists stay sorted). The order of an
entity's relations is no longer a semantic change, so the upgrade does not
raise one mission change per mission for the agents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both maps now draw on one shared base (map-canvas.ts, map-sources.ts,
map-model.ts): the offline gazetteer's countries in three levels of detail
with sea, country and city names, eased zoom about the pointer, a drag that
glides, the nautical-mile scale, the position readout, a hover tip at the
cursor, a remembered legend and a right-click menu. The geography and tiles
are loaded once for the page.

The Operations map keeps its toolbar, roster, layers, saved views, locate
and follow, and adds (owner decisions 2026-10-12):
- clusters by affiliation when far out (a click zooms in; a Layers switch);
- a group made only of facilities as one base marker with its aircraft
  until zoom 11, listing its facilities when clicked;
- mission areas through their reference points in zone order, the selected
  mission's bright with its units ringed;
- type filters (aircraft, surface, submarine, land, weapons, reference
  points, other) and the new switches kept in saved views;
- Shift-click and Shift-drag (own units only) multi-selection; the order
  composer tags the whole selection within its kind rules and 128 cap;
- a right-click menu: draft order, locate, follow, show inside, copy
  coordinates, centre here;
- a feed row about an entity shows it on the map with a ping.
The stored camera keeps CSS-pixel zoom, so views and saved views open as
before on any display scale.

Tiles, for both maps: a coarser tile stands in while one loads, failed
tiles are asked for again later, at most six at a time and 384 kept, the
provider's zoom range and the tile margin are honoured, and tiles are sized
in device pixels (the builder drew them too large on scaled displays).

Builder: unchanged in use; whole-country and whole-sea areas, and theatres
on them, are now drawn from the base map (they were not drawn at all).

Tests: Node tests for the map model, Playwright tests of the Operations map
on a seeded made-up picture at display scales 1, 1.5 and 2, and builder
drawing on the shared base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
COMMAND_CENTER.md describes the map's clusters, facility bases, mission
areas, type filters, selection, menu, feed link and tiles; the operator
guide, the builder's map section, GEOGRAPHY.md and the status and
deviation rows follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plan whose areas came from the drafted concept showed an empty Areas tab
while its theatres were on the map: the tab listed only areas drawn by
hand. It now lists the theatres first, each with its kind, its extent (or
the area it is) and a Show button, then the drawn areas; the tab's count
includes both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Every model request records its submission time when it starts running
  (agents/queue.py), so Scenario Builder calls - all 3,794 requests in the
  operator's store - are timed; flows also record their duration.
- /api/v1/metrics reports latency per role, so the builder's long thinking
  calls do not hide the General's turns.
- The provider-queue note matches the owner decision of 2026-10-03: an
  unknown request releases its slot; nothing waits for an idle confirmation.
- Unresolved actions are split into those that still block a repeat and
  those of archived timelines past ARCHIVED_HOLD_SECONDS (114 from
  2026-09-20..26 on the live store), with a Prometheus gauge.
- With hierarchy management off (the default) the General's menu no longer
  offers operations.revise or resource reservations, which execute() then
  refused, and the initiative rule no longer tells it to use them. The menu
  and the gate share requires_hierarchy() in agents/tools.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The agent scheduler's per-pass gate (settings, picture context, AI Stop),
  run four times a second, and the read-back monitor's next-verification
  read, run twice a second, now read SQLite in a worker thread instead of
  on the event loop that also serves the browser streams.
- World stream packets are JSON-encoded in a worker thread.
- The world stream's delivery metric measured from each entity's evidence
  window, which a deep read can leave minutes old: p95 152 s live on the
  1,435-unit Iran side. Each world change's write time is now kept in memory
  (bounded) and a packet carries written_at, the earliest of its changes;
  the probe times frames from that and leaves frames written before this
  process started untimed. The four world_changes inserts share one helper.
- The grouped-movement guard names groups.remove_units when the General's
  menu offers it, instead of saying no detach control exists.
- doctrine.set_value guidance tells the model to read doctrine.inspect and
  change only settings the scenario leaves player_editable (it set a locked
  weapons-control status twice per live run).
- Command center: a burst of journal entries refreshes status at most once
  a second, and api() reports an HTML or text error page as an ApiError
  instead of a JSON SyntaxError. Shipped JavaScript rebuilt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code health: one great-circle distance, unused code removed, complete examples
Some checks failed
check / check (ubuntu-latest) (pull_request) Has been cancelled
check / check (windows-latest) (pull_request) Has been cancelled
0289dab036
- Six copies of the haversine distance (operational tools, picture,
  observation controller, mobilize, agent events, supervisor predicates)
  now use planning.geodesy.distance_nm / bearing_deg. The tester's byte
  copies and the builder's geo module are unchanged.
- Removed domain/ports.py, which nothing imports, and seven functions
  nothing calls (builder placement/roles, geo, Lua Bench, planning fuel,
  geodesy and packages), with the imports and constant they left unused.
- The Scenario Builder opens the uploaded CMO database with
  trusted_schema=OFF, query_only and no attached databases, as the
  catalog service opens the same file.
- config/example.settings.json lists all 63 settings (13 were missing);
  a test now requires every field in the contract's order.
- config/environment.example documents the TLS, provider CA bundle, proxy
  token, Scenario Builder database/import folder and watchdog variables; a
  test checks it names every variable the controller reads.
- Docs: RUNTIME_HARDENING.md describes the new metric fields and the 30 s
  busy timeout; AI_AGENT_TOOL_CATALOG.md no longer says the game writes stay
  fail-closed in controlled mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vinny manually merged commit 43ddaf6c33 into master 2026-10-13 15:46:17 -04:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
vinny/CMOnet!1
No description provided.