From ab8691383b5947c1efde0642bb677d5a6491f43b Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 3 Nov 2026 14:34:32 +0400 Subject: [PATCH] docs(hud): describe the character-only summary cap and trim comments --- docs/CURRENT-STATE.md | 4 ++-- server/providers/openai/hud-summary.js | 10 +++++----- src/hud.js | 8 +++----- src/hudSummaryResponse.js | 4 ++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index e2b8819ca..a350c832c 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -5408,8 +5408,8 @@ and unreachable upstream (502/504) separately from road geometry. - Input: place/street/nearby-place labels, enabled layers and `feedProvenance`, with layer names as the user sees them; outside English the narration note and per-layer state ids are left out (`overall` carries the state). No model call without a place, street or nearby-place label: the deterministic line is shown. - Language: the HUD sends `locale` (part of the dedupe signature); the proxy keeps a shipped locale, English otherwise. Switching language in place drops the reply and aborts the request in flight, so the next tick asks again. - Reply: `{summary, feedState}` (`text.format` JSON schema, `feedState` a snapshot state or null). A view that is not nominal needs the matching `feedState`. The HUD adds its own translated tag, state first and layer names last (`STALE | … | LIVE FLIGHTS`), so the warning is never clipped. Outside English a reply falls back when it carries a feed-state id, a translated layer's English name or id, only Latin letters in a non-Latin language, or, with every feed healthy, the UNAVAILABLE word. `get_hud_caption` uses the same check and tag order, in English, and returns `feedState`. -- Output keeps at most five words (80 characters), or 20 characters for Japanese and Chinese, cut at a word boundary unless the first word is longer. Errors and the 5 s timeout fall back to the deterministic line; a camera move during typing shows that line instead of the fragment. -- `partial` is a feed state of its own, ranked between `fallback` and `nominal`, so one partial layer no longer reads as off and partial plus nominal reads partial (HUD, voice view state, `analyst_query`, `brief`, the agent world packet). Its narration note says coverage is incomplete rather than not live. +- The model is asked for at most five words; the proxy keeps up to 80 characters, or 20 for Japanese and Chinese, cut at a word boundary unless the first word is longer. It does not count words, since the model reads a name like Rue de Rivoli as one. Errors and the 5 s timeout fall back to the deterministic line; a camera move during typing shows that line instead of the fragment. +- `partial` is a feed state of its own, ranked between `fallback` and `nominal`, so one partial layer, or partial plus nominal, reads partial, not off or nominal (HUD, voice view state, `analyst_query`, `brief`, the agent world packet). Its narration note says coverage is incomplete rather than not live. - A summary asked for before the camera starts moving never paints over the next view: `moveStart` marks the summary dirty and bumps its revision, as `moveEnd` does, aborts the request in flight and stops a reply still typing; neither a reply nor a fallback line (on an error, the 5 s timeout, the abort, or a failed context build) paints once the revision has moved. A dropped request forgets its signature and a timeout counts as a failure, so the next 15 s tick asks again even over the same place. An interrupted typewriter reply also forgets its signature; fully displayed answers remain reusable. ### Place-search providers diff --git a/server/providers/openai/hud-summary.js b/server/providers/openai/hud-summary.js index c65e3763e..7f2ad552f 100644 --- a/server/providers/openai/hud-summary.js +++ b/server/providers/openai/hud-summary.js @@ -52,11 +52,11 @@ function toHudSummary(value, requested) { .split(/\s+/) .map((word) => word.replace(/^['’·・-]+|['’·・-]+$/gu, '')) .filter(Boolean); - // Japanese and Chinese have no spaces to count words by: cap characters. - if (hudSummaryIsUnspaced(locale)) - return capAtWord(words.join(' '), HUD_SUMMARY_MAX_UNSPACED_CHARS, locale); - // No word count: the model counts Rue de Rivoli as one word. - return capAtWord(words.join(' '), HUD_SUMMARY_MAX_CHARS, locale); + // Characters, not words: the model counts Rue de Rivoli as one word. + const max = hudSummaryIsUnspaced(locale) + ? HUD_SUMMARY_MAX_UNSPACED_CHARS + : HUD_SUMMARY_MAX_CHARS; + return capAtWord(words.join(' '), max, locale); } /** diff --git a/src/hud.js b/src/hud.js index 89f9e5cfb..6dbd90c20 100644 --- a/src/hud.js +++ b/src/hud.js @@ -120,11 +120,9 @@ const FEED_STATE_LABELS = Object.freeze({ }); /** - * A summary line with its feed-state warning in the active language, e.g. - * `UNAVAILABLE | | LIVE AIS VESSELS`. The state word leads so the - * one-line readout never clips it; the layer names trail so they never push - * the text out. State ids and layer names stay English in layer data; only - * this line translates them. + * A summary line with its translated warning: + * `UNAVAILABLE | | LIVE AIS VESSELS`. The state leads so a narrow + * readout never clips it; the layer names trail so they never push out the text. */ function withProvenance(text, provenance) { if (!provenance) return text; diff --git a/src/hudSummaryResponse.js b/src/hudSummaryResponse.js index 326ce9efd..72003ef75 100644 --- a/src/hudSummaryResponse.js +++ b/src/hudSummaryResponse.js @@ -78,7 +78,7 @@ export const HUD_SUMMARY_MAX_CHARS = 80; /** feedState values the model may return besides null. */ export const HUD_SUMMARY_FEED_STATES = SNAPSHOT_FEED_STATES; -/** Responses API structured output: the five words plus the canonical state. */ +/** Responses API structured output: the summary plus the canonical state. */ export const HUD_SUMMARY_RESPONSE_FORMAT = Object.freeze({ type: 'json_schema', name: 'hud_summary', @@ -114,7 +114,7 @@ const SUMMARY_LANGUAGES = Object.freeze({ 'zh-Hant': 'Chinese in Traditional characters (mainland wording)', }); -/** Scripts written without spaces, where five words do not apply. */ +/** Scripts written without spaces, capped in characters instead of words. */ const UNSPACED_LOCALES = /^(?:ja|zh)(?:-|$)/; /** Shipped locales not written in Latin letters. */