Skip to content

feat(combo): COMBO_VERTICAL — a bounded vertical-axis combo shaper#23

Open
owengregson wants to merge 1 commit into
release/2.4.5-betafrom
feature/combo-vertical-trim
Open

feat(combo): COMBO_VERTICAL — a bounded vertical-axis combo shaper#23
owengregson wants to merge 1 commit into
release/2.4.5-betafrom
feature/combo-vertical-trim

Conversation

@owengregson

Copy link
Copy Markdown
Owner

What

Adds COMBO_VERTICAL, a third combo "keeper" beside the horizontal pocket servo (COMBO_HOLD) and the reach handicap (COMBO_REACH_HANDICAP). It shapes the fresh vertical knock so a juggled victim reaches a bounded target apex elevation. Default OFF (era-exact no-op).

Base / merge note. This branch was built isolated from the in-flight combo rework, on the common ancestor c815c32 (the merge-base with release/2.4.5-beta). The diff here is the single feature commit; GitHub will 3-way merge it into release/2.4.5-beta, conflicting only on the shared combo files below — that reconciliation is expected (see Integration points). KnockbackEngine (my main kernel seam) is not touched by the parallel work, so it applies clean; the overlap is the compute sites, SessionService, config.yml, Feature, SnapshotParser, ComboPredictor, and MentalPluginV5.

The geometry

A combo keeps a victim un-answerable by holding them where their reach-back is denied. From an elevated victim's higher eye point, the reach-back is a longer hypotenuse, so the horizontal separation at which they can still answer shrinks (sepDeny = 0.3 + sqrt(R_v² − dvertV²), dvertV = max(0, h − 0.18)). More elevation → smaller sepDeny. Elevation is therefore a denial lever, complementary to the reach handicap — and the horizontal servo already predicts the victim's elevation arc to place them at sepDeny, so this fixes the height it reads.

Design

  • Minimal-shaping guard (load-bearing). Combos hold fine at standard KB with no vertical shaping, so the shaping is deliberately small and hard-bounded: the per-hit adjustment to the fresh vertical can never exceed bound (default 0.06 b/t, ≈15% of the 0.4 era launch cap). The bound IS the era-safety guarantee. target-apex defaults to 1.15 — the era apex of a standard 0.4 fresh launch (the "useful apex" the standard knock already reaches, verified against PocketServo.arcHeight) — so at the default a standard knock is held at its own canonical apex and the trim barely moves. A server wanting more denial raises target-apex; the parser hard-caps bound to [0, 0.2] so an operator can only ever make the shaping smaller.
  • Saturation = the "something is wrong" signal. When the bounded solve wants more than the bound, it clamps AND raises VerticalTrim.Result.saturated, surfaced as a combo-vertical … saturated=true line on the KNOCKBACK debug sink. Never exceeds the bound.
  • Mechanism (pure kernel). VerticalTrim inverts the era flight arc — apex is monotone in launch vy, so it bisects for the launch whose apex meets the target (using the stable PocketServo.arcHeight/airTime helpers; the arc sim is never duplicated), then clamps |V' − V0| to the bound. Damp or lift, both bounded. VerticalTrim/VerticalTrimConfig are pure JDK (scalars in — kernel stays Bukkit-free).
  • A3-analog law. The trim adjusts the fresh vertical only — never the friction-carried residual (which rides through inside V0), never the horizontal. Exactly how the horizontal servo scales the fresh horizontal only.

Compose order with the horizontal servo

KnockbackEngine gains an additive computePaced overload taking VerticalTrimConfig:

  1. the vertical trim runs first and fixes the shipped fresh vertical V';
  2. the pocket servo then reads V' (not the era value) for its flight-window / elevation prediction, so σ places the victim at sepDeny for the height actually shipped.

INACTIVE ⇒ the shipped vertical is byte-identical to the era stamp: the existing deepest overload delegates with VerticalTrimConfig.INACTIVE, and KnockbackEngineServoTest passes unchanged.

Latency-compensation precedence

Feature.LATENCY_COMPENSATION predicts the victim's vy at hit-land time (CompensationQuery) and feeds it as victimYOverride. That override is folded into V0 by the era vertical replay (shippedVertical). The trim shapes a vertical that already carries the ping correction and never re-derives victimYOverride, so it composes strictly downstream of latency comp — the two cannot double-apply. Precedence: latency comp fixes where the hit lands (input motion); the trim fixes the apex the fresh launch reaches (output).

Shared detection

The keeper rides the same combo detection COMBO_HOLD opens, so a vertical-only config still detects combos and publishes the victim's combo attacker. On this (isolated) base SessionService.enableCombo/disableCombo are converted from a single boolean into a ref-counted retain/release so detection stays live while any keeper holds it.

Integration points (for the merge into release/2.4.5-beta)

The parallel workstream rebuilt the horizontal servo and the detection substrate (incl. its own "exception-safe detection retain/release"). Every reconcile point carries an // INTEGRATION: comment:

  • SessionService — the ref-counted comboEnabled/enableCombo/disableCombo. Reconcile with the parallel retain/release seam (same intent).
  • ComboVerticalUnit — retain on assemble / release on close (mirrors ComboHoldUnit).
  • KnockbackUnit + HitRegistrationUnit — the comboVerticalTrimFor gate and the predictor-inputs build now covering either keeper; re-apply on top of the reworked compute sites.
  • KnockbackEngine — additive computePaced/explainVerticalTrim overloads + a servo vertical-stamp override; unaffected by the parallel diff, so these apply clean.

Files

Kernel (pure JDK): VerticalTrim, VerticalTrimConfig, KnockbackEngine, VerticalTrimTest (hand-computed pins: OFF=identity, lift, damp, bound saturation+flag, degenerate no-op, launch-height apex shift).

Core: Feature.COMBO_VERTICAL, VerticalTrimSettings, SnapshotParser, ComboVerticalUnit, SessionService, KnockbackUnit, HitRegistrationUnit, ComboPredictor, MentalPluginV5, config.yml, FeatureRegistryTest. GUI surfacing and snapshot defaults auto-derive from the enum.

Gate

./gradlew build green — unit + japicmp + kernel-Bukkit-free + verifyDowngrade/verifyJdk8Api/verifyRelocation/verifyTesterIsolation. Per instructions, integrationTestMatrix was not run; version not bumped; main untouched.

🤖 Generated with Claude Code

Adds a third, orthogonal combo keeper beside the horizontal pocket servo
(COMBO_HOLD) and the reach handicap (COMBO_REACH_HANDICAP): it shapes the
FRESH vertical knock so a juggled victim reaches a bounded target apex. A
higher victim's eye point lengthens their reach-back hypotenuse, so the
separation at which they can answer shrinks — a complement to the reach
handicap. The horizontal servo already predicts the victim's elevation arc
to place them at that deny-separation; this fixes the height it reads.

Minimal-shaping guard (owner directive 2026-07-06). Combos hold fine at
standard KB with no vertical shaping, so the shaping is deliberately small
and HARD-BOUNDED: the per-hit adjustment to the fresh vertical can never
exceed `bound` (default 0.06 b/t ~= 15% of the 0.4 era launch cap). The
bound IS the era-safety guarantee. `target-apex` defaults to 1.15 — the era
apex of a standard 0.4 fresh launch (the "useful apex" the standard knock
already reaches) — so at the default a standard knock is held at its own
canonical apex and the trim barely moves. When the bounded solve wants MORE
than the bound (the "something is wrong" signal), it clamps AND raises the
VerticalTrim.Result.saturated flag, surfaced on the KNOCKBACK debug sink.

Mechanism (pure kernel). VerticalTrim inverts the era flight arc: apex is
monotone in launch vy, so it bisects for the launch whose apex meets the
target (measured with the stable PocketServo.arcHeight/airTime helpers — the
arc sim is never duplicated), then clamps |V' - V0| to the bound. Damp OR
lift, both bounded. VerticalTrim/VerticalTrimConfig are pure JDK (scalars in,
kernel-Bukkit-free). Hand-computed unit pins cover OFF=>identity, lift and
damp within bound, bound saturation (+flag), a degenerate no-op, and the
launch-height apex shift.

Compose order. KnockbackEngine gains an additive computePaced overload: the
vertical trim runs FIRST and fixes the shipped vertical; the pocket servo
then reads THAT shaped vertical (not the era value) for its flight window.
INACTIVE ⇒ the shipped vertical is byte-identical to the era stamp (the
existing deepest overload delegates with VerticalTrimConfig.INACTIVE;
KnockbackEngineServoTest still passes unchanged). A3-analog law: the trim
adjusts the fresh vertical ONLY — never the friction-carried residual (which
rides inside V0), never the horizontal.

Latency-comp precedence. compensationY (latency comp's predicted victim vy
at hit-land time) is folded into V0 by the era vertical replay; the trim
shapes a vertical that ALREADY carries the ping correction and never
re-derives compensationY, so it composes downstream of latency compensation
and the two cannot double-apply.

Shared detection (parallel-safe). The keeper rides the SAME combo detection
COMBO_HOLD opens, so a vertical-only config still detects combos. On this
base SessionService.enableCombo/disableCombo are converted from a single
boolean to a ref-counted retain/release, so detection stays live while ANY
keeper holds it. The concurrent 2.4.5 combo-substrate rebuild is expected to
land the same seam — INTEGRATION comments mark the reconcile points
(SessionService, ComboVerticalUnit, and the two compute sites).

Toggle + wiring. Feature.COMBO_VERTICAL (yamlKey combo-vertical, Family.COMBO,
default OFF, its own VerticalTrimSettings + facets + blurb + icon), the
SnapshotParser case (bound hard-capped to [0, 0.2] so an operator can only
ever shrink the shaping), the ComboVerticalUnit lifecycle, both engine
compute sites (netty pre-send + region), the config.yml module toggle and
documented section, and the registry operator-contract key. Everything else
(GUI surfacing, snapshot defaults) auto-derives from the enum.

Gate: ./gradlew build green — unit + japicmp + kernel-Bukkit-free +
verifyDowngrade/verifyJdk8Api/verifyRelocation/verifyTesterIsolation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01174ZCdfCNQhprYW7YHavYp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant