extract the scroll position into WorldCamera
This commit is contained in:
243
src/test/WorldCameraTest.cpp
Normal file
243
src/test/WorldCameraTest.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#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));
|
||||
}
|
||||
Reference in New Issue
Block a user