feat(combo): COMBO_VERTICAL — a bounded vertical-axis combo shaper#23
Open
owengregson wants to merge 1 commit into
Open
feat(combo): COMBO_VERTICAL — a bounded vertical-axis combo shaper#23owengregson wants to merge 1 commit into
owengregson wants to merge 1 commit into
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).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 → smallersepDeny. 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 atsepDeny, so this fixes the height it reads.Design
bound(default 0.06 b/t, ≈15% of the 0.4 era launch cap). The bound IS the era-safety guarantee.target-apexdefaults to 1.15 — the era apex of a standard 0.4 fresh launch (the "useful apex" the standard knock already reaches, verified againstPocketServo.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 raisestarget-apex; the parser hard-capsboundto[0, 0.2]so an operator can only ever make the shaping smaller.VerticalTrim.Result.saturated, surfaced as acombo-vertical … saturated=trueline on theKNOCKBACKdebug sink. Never exceeds the bound.VerticalTriminverts the era flight arc — apex is monotone in launch vy, so it bisects for the launch whose apex meets the target (using the stablePocketServo.arcHeight/airTimehelpers; the arc sim is never duplicated), then clamps|V' − V0|to the bound. Damp or lift, both bounded.VerticalTrim/VerticalTrimConfigare pure JDK (scalars in — kernel stays Bukkit-free).V0), never the horizontal. Exactly how the horizontal servo scales the fresh horizontal only.Compose order with the horizontal servo
KnockbackEnginegains an additivecomputePacedoverload takingVerticalTrimConfig:V';V'(not the era value) for its flight-window / elevation prediction, so σ places the victim atsepDenyfor the height actually shipped.INACTIVE⇒ the shipped vertical is byte-identical to the era stamp: the existing deepest overload delegates withVerticalTrimConfig.INACTIVE, andKnockbackEngineServoTestpasses unchanged.Latency-compensation precedence
Feature.LATENCY_COMPENSATIONpredicts the victim's vy at hit-land time (CompensationQuery) and feeds it asvictimYOverride. That override is folded intoV0by the era vertical replay (shippedVertical). The trim shapes a vertical that already carries the ping correction and never re-derivesvictimYOverride, 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_HOLDopens, so a vertical-only config still detects combos and publishes the victim's combo attacker. On this (isolated) baseSessionService.enableCombo/disableComboare 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-countedcomboEnabled/enableCombo/disableCombo. Reconcile with the parallel retain/release seam (same intent).ComboVerticalUnit— retain on assemble / release on close (mirrorsComboHoldUnit).KnockbackUnit+HitRegistrationUnit— thecomboVerticalTrimForgate and the predictor-inputs build now covering either keeper; re-apply on top of the reworked compute sites.KnockbackEngine— additivecomputePaced/explainVerticalTrimoverloads + 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 buildgreen — unit + japicmp + kernel-Bukkit-free +verifyDowngrade/verifyJdk8Api/verifyRelocation/verifyTesterIsolation. Per instructions,integrationTestMatrixwas not run; version not bumped;mainuntouched.🤖 Generated with Claude Code