Tighten callout fade comments and docs

This commit is contained in:
German Khokhlov committed 2026-11-06 20:20:11 +04:00
1 parent 910e63a56a
commit c9f40a837c
7 files changed
+19 -29

No files matched your search

+3 -6
View File
@@ -110,10 +110,7 @@ const THEME_MAP = DETECTION_THEME_MAP;
const SPARSE_ZONE_FRACTION = 0.5;
/** Solve cadence for membership/collision; accepted identities still reproject every frame. */
const LABEL_SOLVE_INTERVAL_MS = 125;
/**
* A contact's own card at this opacity or more hides its callout; below it the
* callout fades in, so a card fading in or out never snaps the callout.
*/
/** Own-card opacity that fully hides a callout; below it the callout fades in. */
const CARD_ALPHA_HIDING_CALLOUT = 0.5;
/** Alpha bands retain bracket batching while approximating a continuous radial fade. */
const BRACKET_ALPHA_STEPS = 4;
@@ -1149,8 +1146,8 @@ function _paintCalloutLane(frame) {
ctx.textBaseline = 'alphabetic';
for (let i = 0; i < _calloutCount; i++) {
const row = _calloutPool[i];
// Under the contact's own card (an ambient vessel card) the callout would
// show through the plate. Only a card painting this frame counts.
// The callout would show through its contact's own card: fade it by the
// opacity that card paints with this frame.
const cardAlpha =
row.cardEntryId === ''
? 0
+2 -4
View File
@@ -589,10 +589,8 @@ test('every style paints its OWN plate token through the production callout path
});
test('a callout gives way to its contact\'s own card only while that card paints', () => {
// A vessel's ambient card already shows its name, speed and course, and the
// callout drawn beneath it shows through the card's plate. The host paints
// only part of the published cards (shared budget, collisions, UI chrome),
// so a contact whose card was published but not painted keeps its callout.
// The callout shows through its card's plate. The host paints only some
// published cards (budget, collisions, UI chrome); an unpainted one keeps it.
const env = installEnvironment({ width: 900, height: 600, dpr: 1 });
try {
const ships = [
+1 -2
View File
@@ -505,8 +505,7 @@ export function createQueries({
id: record.name || record.mmsi || 'VESSEL',
type: 'SEA',
skipLabel: record === selected,
// The vessel's ambient card: while the overlay paints it, detection
// draws no callout under it.
// Detection hides the callout while this card paints.
cardSourceId: VESSEL_OVERLAY_SOURCE_ID,
cardEntryId: components.cards.vesselOverlayEntryId(record),
klass: record.type
+1 -2
View File
@@ -23,8 +23,7 @@ export function normalizeVessel(row) {
lastPositionUtc: String(row.last_position_UTC || ''),
lastPositionEpoch: finiteNumber(row.last_position_epoch),
missedRefreshes: 0,
// Filled on first use by the cards' `vesselOverlayEntryId`. The id reads
// only the MMSI, or for an unkeyed row fields it never updates.
// Cached by `vesselOverlayEntryId`; the fields it reads never change.
overlayEntryId: null,
};
}
+5 -6
View File
@@ -1047,9 +1047,9 @@ export function getOverlayPaintRect(sourceId, entryId) {
}
/**
* Opacity an entry will paint with in the frame being painted, or 0 when it is
* not queued. Valid from the custom lanes, which paint before the entries in
* later lanes publish their paint rects. Leaves out only the Cyber sonar fade.
* Opacity an entry paints with this frame, or 0 when it is not queued. Custom
* lanes may call it before later lanes publish paint rects. Leaves out the
* Cyber sonar fade and yield-collision hiding.
* @param {string} sourceId
* @param {string} entryId
* @returns {number}
@@ -2253,9 +2253,8 @@ function addPaintItem(record, placement, temporalAlpha, selected) {
item.collisionHidden = false;
item.stamp = _frameStamp;
_paintQueue[_paintCount++] = item;
// Indexed by source and id rather than by `record.key`, so a lookup builds
// no string. A card fading out after it left its source's entries is still
// queued here, though `_records` no longer has it.
// Keyed by source and id, not `record.key`, so a lookup builds no string.
// Also holds cards fading out after they left `_records`.
let bySource = _queuedItemBySource.get(record.entry.source);
if (!bySource) {
bySource = new Map();
+6 -8
View File
@@ -1224,9 +1224,8 @@ test('custom detection lane receives the shared host frame and paints below ordi
});
test('a detection lane sees which cards this frame will paint before they publish paint rects', () => {
// The detection lane paints first, so a card's paint rectangle for this
// frame does not exist yet when it runs. The queued alpha is what lets a
// contact's callout give way to that contact's own card in the same frame.
// The detection lane paints before the cards publish this frame's paint
// rects, so it reads the queued alpha instead.
const env = installMockEnvironment({ width: 400, height: 300, dpr: 1 });
initWorldOverlay(env.viewer);
const seen = [];
@@ -2529,8 +2528,8 @@ test('promoting an ambient entry to selected paints it once with no fading dupli
});
test('the queued-card index stays bounded while card identities churn', () => {
// The index is internal: capture the per-source map the same way the
// projection-record test captures its map, through Map.prototype.set.
// The index is internal: capture it through Map.prototype.set, like the
// projection-record test.
const originalSet = Map.prototype.set;
let queuedIndex = null;
Map.prototype.set = function captureQueuedIndex(key, value) {
@@ -2546,9 +2545,8 @@ test('the queued-card index stays bounded while card identities churn', () => {
initWorldOverlay(env.viewer);
const FRAMES = 1300;
for (let frame = 0; frame < FRAMES; frame++) {
// One painted card whose identity changes every frame: the same pooled
// paint item serves every id (a vessel cohort republished as the camera
// moves, a CCTV or radio card swapping).
// One card whose id changes every frame reuses one pooled paint item,
// like a vessel cohort republished as the camera moves.
setOverlayEntries('cards', [selectedEntry(`churn-card-${frame}`, {
position: positionAtScreen(200, 150),
})]);