Skip to content

Commit 1b23018

Browse files
Fix false Triki confirms in Quiz and category menus.
Restrict paddle-mode primaryAction to the BLE button edge, add TrikiButtonConfirmGate with cooldown, and document preferButtonConfirm plus adaptive BLE input updates across SDK, sample app, and docs. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e3ff059 commit 1b23018

50 files changed

Lines changed: 2119 additions & 677 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

VeltoKit/GameInput.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ public struct GameInput {
5959
public var gesturePrimed: Bool = false
6060
/// Stores `pointerDirection` used by this scope.
6161
public var pointerDirection: PointerDirection = .center
62+
/// Wykryty tryb BLE notify (fast / normal / lowPower).
63+
public var bleMode: TrikiBLEMode = .unknown
64+
/// Δ`posX` od poprzedniej klatki `pollInput` (SDK).
65+
public var frameDeltaX: Double = 0
66+
/// Δ`posY` od poprzedniej klatki `pollInput` (SDK).
67+
public var frameDeltaY: Double = 0
68+
/// Prędkość z `TrikiMotionEngine` (nie mylić z `intensity` silnika pozycji).
69+
public var trikiVelocity: Double = 0
70+
/// Czy gamepad Triki wykrywa ruch w tej ramce.
71+
public var isMoving: Bool = false
6272

6373
/// Przycisk / impuls strzału (gry typu Dart — edge w logice gry).
6474
public var didShoot: Bool { primaryAction || shotTriggered }
@@ -99,6 +109,11 @@ public struct GameInput {
99109
/// - throwPower: Siła rzutu gestem.
100110
/// - gesturePrimed: Stan uzbrojenia gestu.
101111
/// - pointerDirection: Kierunek wskaźnika.
112+
/// - bleMode: Tryb BLE notify.
113+
/// - frameDeltaX: Δ posX między klatkami.
114+
/// - frameDeltaY: Δ posY między klatkami.
115+
/// - trikiVelocity: Prędkość gamepada Triki.
116+
/// - isMoving: Ruch wykryty przez Triki.
102117
public init(
103118
moveX: Double = 0,
104119
moveY: Double = 0,
@@ -127,7 +142,12 @@ public struct GameInput {
127142
shotTriggered: Bool = false,
128143
throwPower: Double = 0,
129144
gesturePrimed: Bool = false,
130-
pointerDirection: PointerDirection = .center
145+
pointerDirection: PointerDirection = .center,
146+
bleMode: TrikiBLEMode = .unknown,
147+
frameDeltaX: Double = 0,
148+
frameDeltaY: Double = 0,
149+
trikiVelocity: Double = 0,
150+
isMoving: Bool = false
131151
) {
132152
self.moveX = moveX
133153
self.moveY = moveY
@@ -157,5 +177,10 @@ public struct GameInput {
157177
self.throwPower = throwPower
158178
self.gesturePrimed = gesturePrimed
159179
self.pointerDirection = pointerDirection
180+
self.bleMode = bleMode
181+
self.frameDeltaX = frameDeltaX
182+
self.frameDeltaY = frameDeltaY
183+
self.trikiVelocity = trikiVelocity
184+
self.isMoving = isMoving
160185
}
161186
}

VeltoKit/Motion/MotionParser.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ final class MotionParser: ObservableObject {
1414
var retainRecentFrames = true
1515

1616
/// Czas wygładzania kanału tilt.
17-
var tiltAlpha: Double = 0.18
17+
var tiltAlpha: Double = 1.0
1818
/// Czas wygładzania kanału gyro.
19-
var gyroAlpha: Double = 0.32
19+
var gyroAlpha: Double = 1.0
2020
/// Czas wygładzania kanału rotacji.
21-
var rotationAlpha: Double = 0.22
21+
var rotationAlpha: Double = 1.0
2222
/// Minimalna zmiana potrzebna do aktualizacji wartości.
2323
var noiseFloor: Double = 0.001
2424

VeltoKit/MotionConfig.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public struct MotionConfig: Equatable, Sendable {
149149
cfg.axisMapping.invertX = false
150150
cfg.axisMapping.invertY = true
151151
cfg.referenceDriftEnabled = false
152-
cfg.inputSmoothing = 0
152+
cfg.inputSmoothing = 1.0
153153
cfg.deadzone = 0
154154
cfg.pointerSensitivity = 0
155155
cfg.pointerRotDamping = 1
@@ -160,10 +160,10 @@ public struct MotionConfig: Equatable, Sendable {
160160
cfg.paddleAutoCalibBlend = 0.015
161161
cfg.paddleAutoCalibMaxSteer = 0.06
162162
cfg.paddleAutoCalibEnabled = true
163-
cfg.paddleSmoothRetain = 0.58
164-
cfg.paddleSmoothBlend = 0.42
165-
cfg.paddleSmoothRetainIdle = 0.76
166-
cfg.paddleRawSmoothing = 0.22
163+
cfg.paddleSmoothRetain = 0
164+
cfg.paddleSmoothBlend = 1
165+
cfg.paddleSmoothRetainIdle = 0
166+
cfg.paddleRawSmoothing = 1.0
167167
cfg.paddleSmoothMax = 2
168168
cfg.paddleRawDivisor = 100
169169
cfg.paddleRawDeadband = 8
@@ -190,19 +190,20 @@ public struct MotionConfig: Equatable, Sendable {
190190
cfg.deadzone = 0.02
191191
cfg.pointerSensitivity = 0.03
192192
cfg.pointerRotDamping = 0.98
193-
cfg.pointerOutputSmoothing = 0.15
193+
cfg.pointerOutputSmoothing = 0
194+
cfg.inputSmoothing = 1.0
194195
case .gesture:
195196
cfg.sensorInput = .gyro
196197
cfg.axisMapping.inputX = .gyroY
197198
cfg.axisMapping.inputY = .gyroX
198199
cfg.axisMapping.invertX = false
199200
cfg.axisMapping.invertY = true
200201
cfg.referenceDriftEnabled = false
201-
cfg.inputSmoothing = 0.35
202+
cfg.inputSmoothing = 1.0
202203
cfg.deadzone = 0.02
203204
cfg.pointerSensitivity = 0.045
204205
cfg.pointerRotDamping = 0.96
205-
cfg.pointerOutputSmoothing = 0.22
206+
cfg.pointerOutputSmoothing = 0
206207
cfg.gestureThreshold = 0.28
207208
cfg.gestureCooldown = 0.45
208209
cfg.gestureMinRelY = 0.10

VeltoKit/MotionProcessor.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,20 @@ final class MotionProcessor {
138138

139139
let scale = max(0.001, frameScale)
140140
if stableInput != 0 {
141-
let retain = pow(config.paddleSmoothRetain, scale)
142-
smoothX = smoothX * retain + stableInput * (1 - retain)
141+
if config.paddleSmoothBlend >= 1, config.paddleSmoothRetain <= 0 {
142+
smoothX = stableInput
143+
} else {
144+
let retain = pow(config.paddleSmoothRetain, scale)
145+
smoothX = smoothX * retain + stableInput * (1 - retain)
146+
}
143147
} else {
144-
let retainIdle = pow(config.paddleSmoothRetainIdle, scale)
145-
smoothX *= retainIdle
146-
if abs(smoothX) < 0.02 { smoothX = 0 }
148+
if config.paddleSmoothRetainIdle <= 0 {
149+
smoothX = 0
150+
} else {
151+
let retainIdle = pow(config.paddleSmoothRetainIdle, scale)
152+
smoothX *= retainIdle
153+
if abs(smoothX) < 0.02 { smoothX = 0 }
154+
}
147155
}
148156

149157
let cap = max(0.1, config.paddleSmoothMax)

VeltoKit/MotionSDK+Connection.swift

Lines changed: 109 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@ extension MotionSDK {
88
/// Skan BLE + auto-connect do jedynego urządzenia z „triki” w nazwie.
99
public func connect() {
1010
ensureBLEPipeline()
11-
bleManager?.autoConnectWhenSingleLikelyMatch = true
12-
bleManager?.startScan(clearList: true)
11+
trikiController?.ble.autoConnectWhenSingleLikelyMatch = true
12+
trikiController?.connect()
13+
}
14+
15+
/// Ponowne połączenie z ostatnim zapamiętanym urządzeniem (bez pełnego skanu).
16+
public func connectLastDevice() {
17+
ensureBLEPipeline()
18+
trikiController?.ble.autoConnectWhenSingleLikelyMatch = false
19+
trikiController?.ble.connectCachedPeripheralIfAvailable()
1320
}
1421

1522
/// Zamyka połączenie BLE i czyści stan sesji.
@@ -35,21 +42,23 @@ extension MotionSDK {
3542
}
3643

3744
let now = Date().timeIntervalSince1970
38-
if now - lastPacketAt > 0.35, isReceiving { isReceiving = false }
45+
let stale = trikiBLEMode.packetStaleSeconds
46+
if now - lastPacketAt > stale, isReceiving { isReceiving = false }
47+
syncTrikiPublishedState()
3948

4049
if config.mode == .paddle {
4150
parser.refreshTiltSensors()
4251
parser.flushImpulsesOnly()
4352
let impulses = parser.consumeImpulses()
4453
let out = updateFrame(deltaTime: deltaTime)
45-
var enriched = input
46-
enriched.tiltY = parser.sensors.tiltY
47-
enriched.tiltX = parser.sensors.tiltX
48-
enriched.lateral = out.x
49-
enriched.lateralSmooth = out.x
50-
enriched.shake = impulses.shake || (trikiController?.gameInput.isShake ?? false)
51-
enriched.sensors = parser.sensors
52-
applyClickToSensors(&enriched)
54+
var enriched = makeEnrichedGameInput(
55+
output: out,
56+
sdkInput: input,
57+
parser: parser,
58+
impulses: impulses
59+
)
60+
applyTrikiGamepadSignals(&enriched)
61+
finalizeAdaptiveInput(&enriched)
5362
latestEnrichedInput = enriched
5463
publishLiveInputIfNeeded(now: now, input: enriched)
5564
return enriched
@@ -63,14 +72,17 @@ extension MotionSDK {
6372
)
6473
let out = updateFrame(deltaTime: deltaTime)
6574
let impulses = parser.consumeImpulses()
66-
latestEnrichedInput = makeEnrichedGameInput(
75+
var enriched = makeEnrichedGameInput(
6776
output: out,
6877
sdkInput: input,
6978
parser: parser,
7079
impulses: impulses
7180
)
72-
publishLiveInputIfNeeded(now: now, input: latestEnrichedInput)
73-
return latestEnrichedInput
81+
applyTrikiGamepadSignals(&enriched)
82+
finalizeAdaptiveInput(&enriched)
83+
latestEnrichedInput = enriched
84+
publishLiveInputIfNeeded(now: now, input: enriched)
85+
return enriched
7486
}
7587

7688
/// Pobiera ostatnią wzbogaconą ramkę wejścia bez aktualizacji stanu.
@@ -125,6 +137,17 @@ extension MotionSDK {
125137
/// Czyści bufor logów BLE.
126138
public func clearBLEDevLog() { bleManager?.devRawLog.removeAll() }
127139

140+
/// Czy zapisano UUID ostatniego urządzenia (szybkie ponowne połączenie).
141+
public var hasCachedBLEDevice: Bool {
142+
bleManager?.cachedPeripheralUUID != nil
143+
}
144+
145+
/// Log monitora trybu BLE (Δt między pakietami).
146+
public var debugBLEMonitorLogging: Bool {
147+
get { trikiController?.debugBLEMonitorLogging ?? false }
148+
set { trikiController?.debugBLEMonitorLogging = newValue }
149+
}
150+
128151
// MARK: - Private
129152

130153
/// Handles `ensureBLEPipeline`.
@@ -145,7 +168,6 @@ extension MotionSDK {
145168
self.enqueueBLE(bytes)
146169
self.streamParser?.enqueue(data: bytes)
147170
self.lastPacketAt = Date().timeIntervalSince1970
148-
if !self.isReceiving { self.isReceiving = true }
149171
}
150172
.store(in: &bleCancellables)
151173

@@ -156,6 +178,32 @@ extension MotionSDK {
156178
if !connected { self?.resetConnectionState() }
157179
}
158180
.store(in: &bleCancellables)
181+
182+
triki.$bleMode
183+
.removeDuplicates()
184+
.sink { [weak self] mode in
185+
self?.trikiBLEMode = mode
186+
}
187+
.store(in: &bleCancellables)
188+
189+
triki.$idleStatusMessage
190+
.sink { [weak self] message in
191+
self?.trikiIdleStatusMessage = message
192+
}
193+
.store(in: &bleCancellables)
194+
195+
triki.$isReceiving
196+
.removeDuplicates()
197+
.sink { [weak self] receiving in
198+
self?.isReceiving = receiving
199+
}
200+
.store(in: &bleCancellables)
201+
}
202+
203+
func syncTrikiPublishedState() {
204+
guard let triki = trikiController else { return }
205+
trikiBLEMode = triki.getBLEMode()
206+
trikiIdleStatusMessage = triki.idleStatusMessage
159207
}
160208

161209
/// Handles `resetConnectionState`.
@@ -166,7 +214,11 @@ extension MotionSDK {
166214
liveInput = GameInput()
167215
latestEnrichedInput = GameInput()
168216
isReceiving = false
217+
trikiBLEMode = .unknown
218+
trikiIdleStatusMessage = nil
169219
lastHudPublishAt = 0
220+
lastFramePosX = nil
221+
lastFramePosY = nil
170222
}
171223

172224
/// Mapuje wyjście gamepada na silnik pozycji (bez surowych osi w API gry).
@@ -184,13 +236,16 @@ extension MotionSDK {
184236
/// Handles `refreshLiveInputFromEngine`.
185237
func refreshLiveInputFromEngine() {
186238
let out = output
187-
latestEnrichedInput = makeEnrichedGameInput(
239+
var enriched = makeEnrichedGameInput(
188240
output: out,
189241
sdkInput: input,
190242
parser: streamParser,
191243
impulses: (false, false)
192244
)
193-
liveInput = latestEnrichedInput
245+
applyTrikiGamepadSignals(&enriched)
246+
finalizeAdaptiveInput(&enriched)
247+
latestEnrichedInput = enriched
248+
liveInput = enriched
194249
lastHudPublishAt = Date().timeIntervalSince1970
195250
}
196251

@@ -205,6 +260,33 @@ extension MotionSDK {
205260
liveInput = input
206261
}
207262

263+
/// Applies Triki gamepad velocity/tilt edges to `GameInput` (all tryby gier).
264+
func applyTrikiGamepadSignals(_ input: inout GameInput) {
265+
guard let pad = trikiController?.gameInput else { return }
266+
input.tiltLeft = pad.isTiltLeft
267+
input.tiltRight = pad.isTiltRight
268+
let vel = Double(pad.velocity)
269+
input.trikiVelocity = vel
270+
input.isMoving = pad.isMoving
271+
input.intensity = max(input.intensity, vel)
272+
input.flick = input.flick || pad.isSwing
273+
let dir = Double(pad.direction)
274+
if dir != 0, vel > 0.5 {
275+
input.deltaX = dir * vel * 0.012
276+
}
277+
}
278+
279+
/// Uzupełnia `bleMode`, Δpos i strategię adaptacyjną dla gier.
280+
func finalizeAdaptiveInput(_ input: inout GameInput) {
281+
input.bleMode = trikiBLEMode
282+
let prevX = lastFramePosX ?? input.posX
283+
let prevY = lastFramePosY ?? input.posY
284+
input.frameDeltaX = input.posX - prevX
285+
input.frameDeltaY = input.posY - prevY
286+
lastFramePosX = input.posX
287+
lastFramePosY = input.posY
288+
}
289+
208290
/// Builds a UI/game-friendly input snapshot from raw engine output and parser state.
209291
///
210292
/// - Parameters:
@@ -239,16 +321,19 @@ extension MotionSDK {
239321
enriched.sensors = parser.sensors
240322
enriched.pointerDirection = pointerDirection(posX: output.x, posY: output.y)
241323
}
242-
applyClickToSensors(&enriched)
324+
let buttonEdge = impulses.click || sdkInput.primaryAction
325+
if config.mode == .paddle {
326+
enriched.primaryAction = buttonEdge
327+
} else if impulses.click {
328+
enriched.primaryAction = true
329+
}
330+
applyClickToSensors(&enriched, clickEdge: buttonEdge)
243331
return enriched
244332
}
245333

246-
/// Handles `applyClickToSensors`.
247-
///
248-
/// - Parameters:
249-
/// - input: Input used by this operation.
250-
func applyClickToSensors(_ input: inout GameInput) {
251-
guard input.primaryAction else { return }
334+
/// Sets one-shot click flag on sensors when a BLE button edge was detected.
335+
func applyClickToSensors(_ input: inout GameInput, clickEdge: Bool) {
336+
guard clickEdge else { return }
252337
var sensors = input.sensors
253338
sensors.click = true
254339
input.sensors = sensors

0 commit comments

Comments
 (0)