244 lines
9.8 KiB
C++
244 lines
9.8 KiB
C++
#include "catch.hpp"
|
|
|
|
#include "WorldCamera.h"
|
|
#include "WorldConfig.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Slow 10 tiles/s, fast 50 tiles/s, and a 4-tile ramp band — so each ramp spans
|
|
// 2 tiles either side of a contest-zone boundary and the arithmetic stays exact.
|
|
static WorldScroll makeScroll(int rampBandWidth_tiles = 4)
|
|
{
|
|
WorldScroll scroll;
|
|
scroll.panSpeedSlow_tps = 10.0;
|
|
scroll.panSpeedFast_tps = 50.0;
|
|
scroll.panRampBandWidth_tiles = rampBandWidth_tiles;
|
|
return scroll;
|
|
}
|
|
|
|
// The player buffer's right edge — the contest zone's left boundary — sits at 20.
|
|
static WorldRegions makeRegions()
|
|
{
|
|
WorldRegions regions;
|
|
regions.asteroidWidth_tiles = 10;
|
|
regions.playerBufferWidth_tiles = 20;
|
|
regions.contestZoneWidth_tiles = 60;
|
|
regions.enemyBufferWidth_tiles = 10;
|
|
return regions;
|
|
}
|
|
|
|
static ScrollBounds makeBounds(float leftTiles = -100.0f, float rightTiles = 100.0f)
|
|
{
|
|
return ScrollBounds{leftTiles, rightTiles};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Pan speed ramp (REQ-UI-SCROLL-SPEED)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
TEST_CASE("Pan speed is slow over the asteroid and player buffer", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
|
|
// Left of the rising ramp band (which starts at 20 - 2 = 18).
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(-50.0f, 80.0f) == Approx(10.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(0.0f, 80.0f) == Approx(10.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(18.0f, 80.0f) == Approx(10.0f));
|
|
}
|
|
|
|
TEST_CASE("Pan speed is fast across the middle of the contest zone", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
|
|
// Between the two ramp bands: past 20 + 2 and before 80 - 2.
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(22.0f, 80.0f) == Approx(50.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(50.0f, 80.0f) == Approx(50.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(78.0f, 80.0f) == Approx(50.0f));
|
|
}
|
|
|
|
TEST_CASE("Pan speed ramps linearly across each contest-zone boundary", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
|
|
// Rising band spans [18, 22]; the boundary itself is the midpoint.
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(20.0f, 80.0f) == Approx(30.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(19.0f, 80.0f) == Approx(20.0f));
|
|
// Falling band spans [78, 82], mirrored.
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(80.0f, 80.0f) == Approx(30.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(81.0f, 80.0f) == Approx(20.0f));
|
|
}
|
|
|
|
TEST_CASE("Pan speed returns to slow past the enemy stations", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(82.0f, 80.0f) == Approx(10.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(200.0f, 80.0f) == Approx(10.0f));
|
|
}
|
|
|
|
TEST_CASE("The ramp follows the front line as it is pushed", "[camera]")
|
|
{
|
|
// The right boundary is passed in per call, so a push that moves the enemy
|
|
// stations moves the falling ramp with it (REQ-GW-PUSH-EXPAND).
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
|
|
// X = 90 is past the old boundary (slow) but well inside the pushed-back one.
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(90.0f, 80.0f) == Approx(10.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(90.0f, 140.0f) == Approx(50.0f));
|
|
}
|
|
|
|
TEST_CASE("Overlapping ramp bands peak below the fast speed", "[camera]")
|
|
{
|
|
// A contest zone narrower than the ramp band never reaches full speed: the two
|
|
// ramps cross before either tops out, leaving a single peak where they meet.
|
|
const WorldScroll scroll = makeScroll(/*rampBandWidth_tiles*/ 40);
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
|
|
// Bands are [0, 40] rising and [10, 50] falling; they cross at x = 25.
|
|
const float peak = camera.getPanSpeedTilesPerSecondAt(25.0f, 30.0f);
|
|
REQUIRE(peak > 10.0f);
|
|
REQUIRE(peak < 50.0f);
|
|
|
|
// And it really is the maximum — the neighbours on both sides are lower.
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(20.0f, 30.0f) < peak);
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(30.0f, 30.0f) < peak);
|
|
}
|
|
|
|
TEST_CASE("A zero-width ramp band steps straight from slow to fast", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll(/*rampBandWidth_tiles*/ 0);
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(19.99f, 80.0f) == Approx(10.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(50.0f, 80.0f) == Approx(50.0f));
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(80.01f, 80.0f) == Approx(10.0f));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Panning
|
|
// ---------------------------------------------------------------------------
|
|
|
|
TEST_CASE("The camera starts centered on the world origin", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(0.0f));
|
|
}
|
|
|
|
TEST_CASE("Panning moves the view center at the local pan speed", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
WorldCamera camera(scroll, regions);
|
|
|
|
// Starting at 0, the local speed is the slow one: 10 tiles/s for 500 ms.
|
|
REQUIRE(camera.advance(PanDirection::Right, 500, makeBounds()));
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(5.0f));
|
|
|
|
REQUIRE(camera.advance(PanDirection::Left, 500, makeBounds()));
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(0.0f));
|
|
}
|
|
|
|
TEST_CASE("Not panning leaves the view center alone", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
WorldCamera camera(scroll, regions);
|
|
|
|
camera.advance(PanDirection::Right, 500, makeBounds());
|
|
const float before = camera.getViewCenterXTiles();
|
|
|
|
REQUIRE_FALSE(camera.advance(PanDirection::None, 500, makeBounds()));
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(before));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Clamping (REQ-GW-SCROLL-LIMIT)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
TEST_CASE("Panning stops at the scroll bounds", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
WorldCamera camera(scroll, regions);
|
|
|
|
// Far more time than it takes to cross the bound.
|
|
camera.advance(PanDirection::Right, 100000, makeBounds(-5.0f, 12.0f));
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(12.0f));
|
|
|
|
camera.advance(PanDirection::Left, 100000, makeBounds(-5.0f, 12.0f));
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(-5.0f));
|
|
}
|
|
|
|
TEST_CASE("Shrinking bounds pull the view in even without panning", "[camera]")
|
|
{
|
|
// Bounds move as the game progresses, so clamping cannot wait for the player
|
|
// to press a key — a view left outside them would show unreachable world.
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
WorldCamera camera(scroll, regions);
|
|
|
|
camera.advance(PanDirection::Right, 100000, makeBounds(-50.0f, 40.0f));
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(40.0f));
|
|
|
|
// The right bound comes in; the camera must follow it despite no pan input,
|
|
// and must report that it moved.
|
|
REQUIRE(camera.advance(PanDirection::None, 16, makeBounds(-50.0f, 25.0f)));
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(25.0f));
|
|
}
|
|
|
|
TEST_CASE("Panning into a bound the view already sits on reports no movement",
|
|
"[camera]")
|
|
{
|
|
// The caller refreshes the box-select rectangle on a true return, so a camera
|
|
// pinned against its limit must not keep claiming to have moved.
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
WorldCamera camera(scroll, regions);
|
|
|
|
camera.advance(PanDirection::Right, 100000, makeBounds(-5.0f, 12.0f));
|
|
REQUIRE_FALSE(camera.advance(PanDirection::Right, 16, makeBounds(-5.0f, 12.0f)));
|
|
}
|
|
|
|
TEST_CASE("Reset returns the view to the origin", "[camera]")
|
|
{
|
|
const WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
WorldCamera camera(scroll, regions);
|
|
|
|
camera.advance(PanDirection::Right, 2000, makeBounds());
|
|
REQUIRE(camera.getViewCenterXTiles() != Approx(0.0f));
|
|
|
|
camera.reset();
|
|
REQUIRE(camera.getViewCenterXTiles() == Approx(0.0f));
|
|
}
|
|
|
|
TEST_CASE("The camera tracks config edited after construction", "[camera]")
|
|
{
|
|
// The camera references the config rather than copying it, so a restart that
|
|
// reloads world.toml in place (REQ-CFG-RELOAD) changes the pan speed without
|
|
// the camera being rebuilt.
|
|
WorldScroll scroll = makeScroll();
|
|
const WorldRegions regions = makeRegions();
|
|
const WorldCamera camera(scroll, regions);
|
|
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(0.0f, 80.0f) == Approx(10.0f));
|
|
scroll.panSpeedSlow_tps = 3.0;
|
|
REQUIRE(camera.getPanSpeedTilesPerSecondAt(0.0f, 80.0f) == Approx(3.0f));
|
|
}
|