Further improve special/splatted calc

This commit is contained in:
Kalle
2026-09-25 20:44:35 +03:00
parent ba55e0f41d
commit cd99cb6d54
9 changed files with 94 additions and 14 deletions

View File

@@ -269,7 +269,9 @@ sequenceDiagram
a grey X, which a blown-out backdrop turns near-white while a wash stays
tinted at every pulse phase (a big dark weapon render such as the
Nautilus drum dilutes that tint, but a diluted wash still reads pale,
which a tinted splat never does), and a ready read
which a tinted splat never does; a pale backdrop showing through a
splat's translucent plate tints it like a wash, but the X's grey strokes
over the dark squid still give it away), and a ready read
must also see a washed (ink-poor) body: pale backdrop, a ship's hull or
the lead banner leaking past an icon edge fakes the shoulder glow, and a ready the shoulder glow does
not corroborate needs the body's ink gone rather than merely paled,

View File

@@ -46,6 +46,8 @@ import type { DetectedEvent, Detector, GateResult } from "../types";
import {
badgeRoi,
CARD_LAYOUTS,
CROSS_BLEED_MIN_FRACTION,
CROSS_BLEED_MIN_LAPLACIAN,
CROSS_MIN_FRACTION,
CROSS_MIN_LAPLACIAN,
CROSS_SATURATION_MIN,
@@ -182,6 +184,17 @@ function minTopCorner(
return { mean: Math.min(...means), saturation: satSum / n };
}
/** the cross probe's saturated strokes are crisp enough to be the X, not scene bleed */
function crossedOut(crossFraction: number, crossLap: number): boolean {
if (crossFraction < CROSS_MIN_FRACTION) return false;
return (
crossLap >=
(crossFraction > CROSS_BLEED_MIN_FRACTION
? CROSS_BLEED_MIN_LAPLACIAN
: CROSS_MIN_LAPLACIAN)
);
}
/** fraction of the probe that is saturated-and-bright (cross-out strokes) */
function saturatedFraction(hsv: Mat, roi: Roi): number {
const m = copyRoi(hsv, roi);
@@ -452,9 +465,7 @@ export function createMinimapDetector(
}
const crossFraction = saturatedFraction(hsv, layout.cross);
const crossLap = meanBrightness(lap, layout.cross);
const occluded =
crossFraction >= CROSS_MIN_FRACTION &&
crossLap >= CROSS_MIN_LAPLACIAN;
const occluded = crossedOut(crossFraction, crossLap);
const corner = minTopCorner(gray, hsv, layout.weapon);
const lightSurface =
corner.mean >= SPECIAL_READY_MIN_CORNER_MEAN &&
@@ -623,8 +634,7 @@ export function createMinimapDetector(
}
const crossFraction = saturatedFraction(hsv, layout.cross);
const crossLap = meanBrightness(lap, layout.cross);
const occluded =
crossFraction >= CROSS_MIN_FRACTION && crossLap >= CROSS_MIN_LAPLACIAN;
const occluded = crossedOut(crossFraction, crossLap);
const corner = minTopCorner(gray, hsv, layout.weapon);
const lightSurface =
corner.mean >= SPECIAL_READY_MIN_CORNER_MEAN &&
@@ -643,8 +653,7 @@ export function createMinimapDetector(
}
const crossFraction = saturatedFraction(hsv, enemyCrossRoi(cy));
const crossLap = meanBrightness(lap, enemyCrossRoi(cy));
const occluded =
crossFraction >= CROSS_MIN_FRACTION && crossLap >= CROSS_MIN_LAPLACIAN;
const occluded = crossedOut(crossFraction, crossLap);
// light camo rows: pick template variant by corner brightness, raise ink threshold past it
const corner = minTopCorner(gray, hsv, weaponRoi);
const lightSurface =

View File

@@ -113,11 +113,20 @@ export const CROSS_VALUE_MIN = 110;
export const CROSS_MIN_FRACTION = 0.08;
/**
* Saturation alone is not a cross-out: the POV map screen bleeds team ink
* through the cards (phantom fractions 0.61-0.65 on Um'ami, everyone alive).
* The X is crisp while bleed is softened: mean |Laplacian| struck >=98 vs bleed
* <=34 (clean cards read up to ~62 of line art at fraction ~0).
* through the cards (phantom fractions 0.56-0.70 at mean |Laplacian| 22-42 on
* the Um'ami and Triton cup VoDs, everyone alive), while the X is crisp
* (clean cards read up to ~62 of line art, but at fraction ~0). The X is drawn
* in the card owner's pale team tint, so over a bright team-colored scene
* (Triton cup VoD, yellow team) its strokes soften: 21 struck cards and rows
* there read 41-65 at fractions 0.11-0.34. Bleed saturates far more of the
* probe, so past CROSS_BLEED_MIN_FRACTION only the crisp floor counts — the
* two soft struck reads up there (0.56 at 55, 0.66 at 33) stay lost. Margins
* are THIN (bleed 0.56 at 41.6 vs struck 0.15 at 41.5) — re-measure before
* moving any.
*/
export const CROSS_MIN_LAPLACIAN = 65;
export const CROSS_MIN_LAPLACIAN = 40;
export const CROSS_BLEED_MIN_FRACTION = 0.5;
export const CROSS_BLEED_MIN_LAPLACIAN = 65;
/** Sub-tile silhouettes matched shape-only (specials.ts) to split near-tied main-weapon icons. */
export const SUB_TILE_TEMPLATE_SIZES = [24, 27, 30, 33, 36] as const;

View File

@@ -33,6 +33,8 @@ import {
STATUS_COMB_GAP_HALF_WIDTH,
STATUS_COMB_MAX_SHIFT,
STATUS_COMB_SIDE_SPANS,
STATUS_CROSSED_MIN_BODY_DARK,
STATUS_CROSSED_MIN_BODY_GREY,
STATUS_DARK_MAX_VALUE,
STATUS_DEAD_MAX_BODY_INK,
STATUS_DEAD_MAX_SHOULDER_GLOW,
@@ -447,7 +449,8 @@ function sideDecisiveness(reads: SlotRead[]): number {
* backdrop turns the plate near-white, or the trough dims the wash under both
* ready floors. Where a big dark weapon render dilutes the wash's tint as low
* as a splat over a bright tinted backdrop reads, the splat's grey X strokes
* still tell them apart. Only unsaturated glow counts as the wash's
* still tell them apart, and they veto a tinted body outright when the dark
* squid shows under them (a splat over a pale tinted backdrop). Only unsaturated glow counts as the wash's
* (STATUS_GLOW_MAX_SPREAD): bright team ink lights the shoulder on its own
* once the ink is light enough (orange clears the glow floor, lime does not).
* The wash also replaces the body's ink, so an ink-heavy body means backdrop
@@ -471,8 +474,11 @@ function classifySlot(
// the wash glows pale on every layout; raw brightness is team ink or backdrop
const washGlow = shoulderPaleGlow;
const inkPoor = bodyInk <= STATUS_DEAD_MAX_BODY_INK;
const crossed =
bodyGrey >= STATUS_CROSSED_MIN_BODY_GREY &&
bodyDark >= STATUS_CROSSED_MIN_BODY_DARK;
const tinted =
bodyTint >= STATUS_WASH_MIN_BODY_TINT ||
(bodyTint >= STATUS_WASH_MIN_BODY_TINT && !crossed) ||
(bodyTint >= STATUS_UNCROSSED_WASH_MIN_BODY_TINT &&
bodyGrey <= STATUS_UNCROSSED_WASH_MAX_BODY_GREY &&
bodyDark <= STATUS_UNCROSSED_WASH_MAX_BODY_DARK);

View File

@@ -247,6 +247,19 @@ export const STATUS_UNCROSSED_WASH_MIN_BODY_TINT = 0.2;
export const STATUS_UNCROSSED_WASH_MAX_BODY_GREY = 0.15;
export const STATUS_UNCROSSED_WASH_MAX_BODY_DARK = 0.45;
/**
* ...and a body the X does cross is never a wash, however tinted: a pale teal
* or sky backdrop through the translucent plate lifts a splat's tint to
* 0.30-0.46 (Triton cup VoD, 21 such splats over the whole VoD). Neither
* stroke class alone splits them from washes: grey or two-tone weapon renders
* put washes at grey up to 0.26, and dark renders (Nautilus drum, frying pan)
* at dark up to 0.35. Together they do: those splats read grey >=0.21 at dark
* >=0.17, while every wash reading grey >=0.2 reads dark <=0.16. Margins are
* THIN on dark (0.16 vs 0.17) — re-measure before moving either.
*/
export const STATUS_CROSSED_MIN_BODY_GREY = 0.2;
export const STATUS_CROSSED_MIN_BODY_DARK = 0.17;
/** Special ready: shoulder glow past this (attested >=0.40 vs <=0.06). */
export const STATUS_READY_MIN_SHOULDER_GLOW = 0.25;

View File

@@ -0,0 +1,21 @@
{
"event": "Minimap",
"data": {
"spectator": false,
"teammates": [
{ "self": false, "dead": false },
{ "self": false, "dead": false },
{ "self": false, "dead": false },
{ "self": true, "dead": true }
],
"enemies": [
{ "dead": false },
{ "dead": false },
{ "dead": false },
{ "dead": false }
]
},
"options": {
"notes": "Triton cup VoD (v2811381928, 720p S3 POV), Museum d'Alfonsino SZ at 1:21, t=1782.25. The POV player (have faith, .52 Gal) was splatted at 1:22 and this is the respawn map fading in. The self card is struck through, but the X's pale strokes over a light yellow backdrop are soft on this frame: the cross probe reads saturated 0.33, while its crispness reads 49 |Laplacian|, under the 65 floor. The card read as alive, and that one read split the player's death on the timeline into a band too short to be real, which was debounced away. Half a second later the same card reads 81. Names, weapons and abilities are soft mid-fade and are not asserted."
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -0,0 +1,20 @@
{
"event": "PlayerStatus",
"data": {
"layout": "narrow-right",
"cast": null,
"time": 206,
"special": [
[true, false, false, false],
[false, false, false, false]
],
"dead": [
[false, true, false, false],
[false, false, false, false]
]
},
"options": {
"skipFields": ["layout"],
"notes": "Triton cup VoD (v2811381928, 720p S3 POV), Museum d'Alfonsino SZ at 3:26, t=1657. Left slot 1 (Custom Blaster) is splatted from 3:27 to 3:24: a dark squid under a grey X, with a pale teal backdrop showing through the translucent plate. That backdrop tints the ink-poor body to 0.33-0.45, past the 0.30 wash floor, so the splat read as special-ready on two of its four reads. The remaining lone dead read was then debounced away, and the match lost one of the player's 8 deaths. The X strokes still show: grey 0.23-0.36, where every attested wash at that tint reads <=0.20. Left slot 0 holds a real wash. The strip sits at an in-between pitch here (left apexes ~[590,670,753,833]), so the layout is not asserted."
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB