diff --git a/Assets/MagnetismHeroes/Tests/PlayMode/PlaygroundSafetyPlayTests.cs b/Assets/MagnetismHeroes/Tests/PlayMode/PlaygroundSafetyPlayTests.cs index 2807778..aaed7f3 100644 --- a/Assets/MagnetismHeroes/Tests/PlayMode/PlaygroundSafetyPlayTests.cs +++ b/Assets/MagnetismHeroes/Tests/PlayMode/PlaygroundSafetyPlayTests.cs @@ -182,11 +182,15 @@ namespace MagnetismHeroes.Tests Assert.That(_app.Presenter.CanDropGroup("group-0"), Is.True); var validDelta = _app.Presenter.DropDelta; var validPoint = _app.Presenter.Balls["safety-2"].transform.position; _app.Input.Move(point + new Vector2(140, 0)); - Assert.That(_app.Presenter.DropDelta, Is.EqualTo(validDelta)); - Assert.That(_app.Presenter.Balls["safety-2"].transform.position, Is.EqualTo(validPoint)); + var stopped = _app.Presenter.Balls["safety-2"].transform.position; + Assert.That(stopped.x, Is.GreaterThan(validPoint.x + .2f), "Continue the safe part of the split drag up to the obstacle."); + Assert.That(stopped.x, Is.EqualTo(.689f).Within(.003f), "The whole unit sphere must stop just before the thin wall."); + Assert.That(_app.Presenter.DropDelta.x, Is.GreaterThan(validDelta.x + .2f)); + var corePreview = _app.Session.Board.PreviewSplit("group-0", new[] { "safety-2" }, BoardPresenter.ToPosition(_app.Presenter.DropDelta)); + foreach (var ball in corePreview) Assert.That(_app.Presenter.Balls[ball.id].transform.position, Is.EqualTo(BoardPresenter.ToVector(ball.position))); _app.Input.Move(point + new Vector2(128, 0)); var reverse = _app.Presenter.Balls["safety-2"].transform.position; - Assert.That(reverse.x, Is.LessThan(validPoint.x - .01f)); + Assert.That(reverse.x, Is.LessThan(stopped.x - .01f), "Reverse must act immediately at the last safe split delta."); var preview = _app.Presenter.Balls.ToDictionary(pair => pair.Key, pair => pair.Value.transform.position); _app.Input.End(point + new Vector2(128, 0)); yield return null; Assert.That(_app.Session.Board.Groups.Length, Is.EqualTo(2)); diff --git a/Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs b/Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs index 4fe7bc0..94a76cd 100644 --- a/Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs +++ b/Assets/MagnetismHeroes/UnityRuntime/Presentation/BoardPresenter.cs @@ -144,20 +144,40 @@ namespace MagnetismHeroes.UnityRuntime.Presentation CancelPreview(); _previewGroup = groupId; _splitPreview = true; _previewValid = false; return false; } + if (!CanSweepSplit(positions, selected)) + { + if (!hadValidPreview) { _previewValid = false; return false; } + // Search Core input deltas, so the last safe arrays and the committed delta stay identical. + var previousDelta = DropDelta; + positions = _board.PreviewSplit(groupId, selected, ToPosition(previousDelta)); + float safe = 0, blocked = 1; + for (int iteration = 0; iteration < 20; iteration++) + { + float fraction = (safe + blocked) * .5f; + var candidateDelta = Vector3.Lerp(previousDelta, delta, fraction); + var candidate = _board.PreviewSplit(groupId, selected, ToPosition(candidateDelta)); + if (candidate != null && candidate.All(ball => _environment.IsSafeSphere(ToVector(ball.position), transform)) + && CanSweepSplit(candidate, selected)) + { safe = fraction; positions = candidate; } + else blocked = fraction; + } + delta = Vector3.Lerp(previousDelta, delta, safe); + } + foreach (var ball in positions) _balls[ball.id].transform.position = ToVector(ball.position); + DropDelta = delta; _previewValid = true; + PositionBadges(); return true; + } + + private bool CanSweepSplit(IReadOnlyList positions, IReadOnlyList selected) + { foreach (var ball in positions.Where(ball => selected.Contains(ball.id))) { var start = _balls[ball.id].transform.position; var movement = ToVector(ball.position) - start; if ((_environment.ConstrainTranslation(new[] { start }, movement, transform) - movement).sqrMagnitude > .00000001f) - { - // Keep exactly the last Core-produced split and its input delta; never commit a clipped shape. - _previewValid = hadValidPreview; - return hadValidPreview; - } + return false; } - foreach (var ball in positions) _balls[ball.id].transform.position = ToVector(ball.position); - DropDelta = delta; _previewValid = true; - PositionBadges(); return true; + return true; } public void ShowSelection(IReadOnlyCollection selected)