// Smoke test del parser d'animacions amb suport de flip H/V. Stand-alone: // només enllaça `animated_sprite_parser.cpp` (la part del motor que NO toca // Resource::Cache ni Surface), perquè la cobertura interessa el contracte // de càrrega del YAML, no el render real. // // Tests coberts: // T1 backward compat — YAML sense cap flip declarat carrega tots els // frames amb (flip_h=false, flip_v=false) i sense // que l'animació prengui control del flip. // T2 flip a nivell d'animació — `flip_h: true` amb frames `int` → // tots els frames hereten flip_h=true. // T3 override per-frame — frame mapping `{ index: N, flip_h: true }` // en una animació sense flip → només aquest frame // té el flag. // T4 mapping no hereta — animació amb `flip_h: true` però un frame // `{ index: N }` (mapping sense flip) → flip_h=false // en aquest frame (el mapping declara el seu propi // estat, sense heretar). // T5 combinat V+H — frame `{ index, flip_h: true, flip_v: true }` → // ambdós flags actius. #include #include #include #include "core/rendering/sprite/animated_sprite.hpp" #include "external/fkyaml_node.hpp" namespace { int g_failures = 0; void check(bool cond, const char* msg, const char* file, int line) { if (!cond) { std::cerr << " FAIL: " << msg << " (" << file << ":" << line << ")\n"; ++g_failures; } } #define REQUIRE(cond) check((cond), #cond, __FILE__, __LINE__) // Paràmetres comuns per a tots els tests: spritesheet hipotètic 64×64 // amb tiles de 16×16 → grid 4×4, max_tiles=16. auto sheetParams() -> AnimatedSprite::SheetParams { return AnimatedSprite::SheetParams{ .frame_width = 16.0F, .frame_height = 16.0F, .frames_per_row = 4, .max_tiles = 16, }; } auto parse(const std::string& yaml_str) { return AnimatedSprite::parseAnimationsFromYaml( fkyaml::node::deserialize(yaml_str), sheetParams()); } void testBackwardCompat() { std::cout << "[T1] backward compat: cap flip declarat\n"; const std::string YAML = R"( animations: walk: { frames: [0, 1, 2, 3], frame_ms: 100 } stand: { frames: [4] } )"; const auto ANIMS = parse(YAML); REQUIRE(ANIMS.size() == 2); const auto WALK_IT = ANIMS.find("walk"); REQUIRE(WALK_IT != ANIMS.end()); if (WALK_IT != ANIMS.end()) { const auto& WALK = WALK_IT->second; REQUIRE(WALK.frames.size() == 4); REQUIRE(!WALK.controls_flip); for (const auto& F : WALK.frames) { REQUIRE(!F.flip_h); REQUIRE(!F.flip_v); } } const auto STAND_IT = ANIMS.find("stand"); REQUIRE(STAND_IT != ANIMS.end()); if (STAND_IT != ANIMS.end()) { const auto& STAND = STAND_IT->second; REQUIRE(STAND.frames.size() == 1); REQUIRE(!STAND.controls_flip); REQUIRE(!STAND.frames[0].flip_h); REQUIRE(!STAND.frames[0].flip_v); } } void testFlipAtAnimationLevel() { std::cout << "[T2] flip a nivell d'anim hereten frames int\n"; const std::string YAML = R"( animations: walk_west: { frames: [8, 9, 10, 11], frame_ms: 100, flip_h: true } )"; const auto ANIMS = parse(YAML); const auto IT = ANIMS.find("walk_west"); REQUIRE(IT != ANIMS.end()); if (IT != ANIMS.end()) { const auto& A = IT->second; REQUIRE(A.frames.size() == 4); REQUIRE(A.controls_flip); for (const auto& F : A.frames) { REQUIRE(F.flip_h); REQUIRE(!F.flip_v); } } } void testOverridePerFrame() { std::cout << "[T3] override per-frame en animació sense flip\n"; const std::string YAML = R"( animations: death_spin: frames: - 0 - 4 - 8 - { index: 8, flip_h: true } frame_ms: 100 )"; const auto ANIMS = parse(YAML); const auto IT = ANIMS.find("death_spin"); REQUIRE(IT != ANIMS.end()); if (IT != ANIMS.end()) { const auto& A = IT->second; REQUIRE(A.frames.size() == 4); // Hi ha un mapping → l'animació pren el control del flip. REQUIRE(A.controls_flip); // Els tres primers (int, sense flip a l'anim) → tot a false. REQUIRE(!A.frames[0].flip_h); REQUIRE(!A.frames[0].flip_v); REQUIRE(!A.frames[1].flip_h); REQUIRE(!A.frames[1].flip_v); REQUIRE(!A.frames[2].flip_h); REQUIRE(!A.frames[2].flip_v); // El quart (mapping amb flip_h: true) → només flip_h. REQUIRE(A.frames[3].flip_h); REQUIRE(!A.frames[3].flip_v); } } void testMappingDoesNotInherit() { std::cout << "[T4] mapping NO hereta el flip de l'animació\n"; const std::string YAML = R"( animations: mixed: frames: - 0 - { index: 4 } frame_ms: 100 flip_h: true )"; const auto ANIMS = parse(YAML); const auto IT = ANIMS.find("mixed"); REQUIRE(IT != ANIMS.end()); if (IT != ANIMS.end()) { const auto& A = IT->second; REQUIRE(A.frames.size() == 2); REQUIRE(A.controls_flip); // Frame int hereta el flip_h true de l'animació. REQUIRE(A.frames[0].flip_h); REQUIRE(!A.frames[0].flip_v); // Frame mapping declara els seus propis flags (cap declarat // → false) i NO hereta del flip a nivell d'anim. REQUIRE(!A.frames[1].flip_h); REQUIRE(!A.frames[1].flip_v); } } void testCombinedVerticalAndHorizontal() { std::cout << "[T5] flip combinat V+H per-frame\n"; const std::string YAML = R"( animations: upside_down: frames: - { index: 5, flip_h: true, flip_v: true } frame_ms: 100 )"; const auto ANIMS = parse(YAML); const auto IT = ANIMS.find("upside_down"); REQUIRE(IT != ANIMS.end()); if (IT != ANIMS.end()) { const auto& A = IT->second; REQUIRE(A.frames.size() == 1); REQUIRE(A.controls_flip); REQUIRE(A.frames[0].flip_h); REQUIRE(A.frames[0].flip_v); } } } // namespace auto main() -> int { std::cout << "== smoke tests: animation flip parser ==\n"; testBackwardCompat(); testFlipAtAnimationLevel(); testOverridePerFrame(); testMappingDoesNotInherit(); testCombinedVerticalAndHorizontal(); if (g_failures == 0) { std::cout << "ALL OK\n"; return 0; } std::cerr << g_failures << " smoke check(s) failed\n"; return 1; }