From 0b99911f5d1cc8b26c14aaa2cdd5000e2c2a325f Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sun, 21 Dec 2025 18:43:40 +0100 Subject: [PATCH] fixed i block --- src/gameplay/coop/CoopGame.cpp | 3 +- src/graphics/renderers/GameRenderer.cpp | 67 ++++++++++++++++--------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/gameplay/coop/CoopGame.cpp b/src/gameplay/coop/CoopGame.cpp index 0d8abf8..8b29b4c 100644 --- a/src/gameplay/coop/CoopGame.cpp +++ b/src/gameplay/coop/CoopGame.cpp @@ -258,7 +258,8 @@ bool CoopGame::cellFilled(const Piece& p, int cx, int cy) { const Shape& shape = SHAPES[p.type]; uint16_t mask = shape[p.rot % 4]; int bitIndex = cy * 4 + cx; - return (mask >> (15 - bitIndex)) & 1; + // Masks are defined row-major 4x4 with bit 0 = (0,0) (same convention as classic). + return (mask >> bitIndex) & 1; } void CoopGame::clearCompletedLines() { diff --git a/src/graphics/renderers/GameRenderer.cpp b/src/graphics/renderers/GameRenderer.cpp index 09a82ac..c845404 100644 --- a/src/graphics/renderers/GameRenderer.cpp +++ b/src/graphics/renderers/GameRenderer.cpp @@ -1864,7 +1864,7 @@ void GameRenderer::renderCoopPlayingState( struct SmoothState { bool initialized{false}; uint64_t seq{0}; float visualX{0.0f}; float visualY{0.0f}; }; static SmoothState s_leftSmooth{}; static SmoothState s_rightSmooth{}; - struct SpawnFadeState { bool active{false}; uint64_t seq{0}; Uint32 startTick{0}; float durationMs{200.0f}; CoopGame::Piece piece; float targetX{0.0f}; float targetY{0.0f}; float tileSize{0.0f}; }; + struct SpawnFadeState { bool active{false}; uint64_t seq{0}; Uint32 startTick{0}; float durationMs{200.0f}; CoopGame::Piece piece; int spawnY{0}; float targetX{0.0f}; float targetY{0.0f}; float tileSize{0.0f}; }; static SpawnFadeState s_leftSpawnFade{}; static SpawnFadeState s_rightSpawnFade{}; @@ -2281,11 +2281,15 @@ void GameRenderer::renderCoopPlayingState( sf.durationMs = 200.0f; sf.seq = seq; sf.piece = game->current(side); + sf.spawnY = sf.piece.y; sf.tileSize = finalBlockSize; - // Note: targetX/targetY are recomputed during drawing using the live - // current piece so movement/rotation during the fade stays correct. - sf.targetX = gridX + static_cast(sf.piece.x) * finalBlockSize; - sf.targetY = gridY; + // Note: during the spawn fade we draw the live piece each frame. + // If the piece is still above the visible grid, we temporarily pin + // it so the topmost filled cell appears at row 0 (no spawn delay), + // while still applying smoothing offsets so it starts moving + // immediately. + sf.targetX = 0.0f; + sf.targetY = 0.0f; } else { // Reuse exact horizontal smoothing from single-player constexpr float HORIZONTAL_SMOOTH_MS = 55.0f; @@ -2358,10 +2362,7 @@ void GameRenderer::renderCoopPlayingState( return std::pair{ offsetX, offsetY }; }; - // Draw any active spawn fades (alpha ramp into first row). Draw before - // the regular active pieces; while the spawn fade is active the piece's - // real position is above the grid and will not be drawn by drawPiece. - auto drawSpawnFadeIfActive = [&](SpawnFadeState &sf, CoopGame::PlayerSide side) { + auto drawSpawnFadeIfActive = [&](SpawnFadeState &sf, CoopGame::PlayerSide side, const std::pair& offsets) { if (!sf.active) return; // If the piece has already changed, stop the fade. @@ -2377,36 +2378,56 @@ void GameRenderer::renderCoopPlayingState( Uint8 alpha = static_cast(std::lround(255.0f * t)); if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, alpha); - // Align the topmost filled cell to row 0 (first visible row), like classic. int minCy = 4; + int maxCy = -1; for (int cy = 0; cy < 4; ++cy) { for (int cx = 0; cx < 4; ++cx) { if (!CoopGame::cellFilled(livePiece, cx, cy)) continue; minCy = std::min(minCy, cy); + maxCy = std::max(maxCy, cy); } } if (minCy == 4) { minCy = 0; } - sf.targetX = gridX + static_cast(livePiece.x) * sf.tileSize; - sf.targetY = gridY - static_cast(minCy) * sf.tileSize; + if (maxCy < 0) { + maxCy = 0; + } - // Draw the live piece at the fade target. + // Pin only when *no* filled cell is visible yet. Using maxCy avoids pinning + // cases like vertical I where some blocks are already visible at spawn. + const bool pinToFirstVisibleRow = (livePiece.y + maxCy) < 0; + + const float baseX = gridX + static_cast(livePiece.x) * sf.tileSize + offsets.first; + float baseY = 0.0f; + if (pinToFirstVisibleRow) { + // Keep the piece visible (topmost filled cell at row 0), but also + // incorporate real y-step progression so the fall accumulator wrapping + // doesn't produce a one-row snap. + const int dySteps = livePiece.y - sf.spawnY; + baseY = (gridY - static_cast(minCy) * sf.tileSize) + + static_cast(dySteps) * sf.tileSize + + offsets.second; + } else { + baseY = gridY + static_cast(livePiece.y) * sf.tileSize + offsets.second; + } + + // Draw the live piece (either pinned-to-row0 or at its real position). for (int cy = 0; cy < 4; ++cy) { for (int cx = 0; cx < 4; ++cx) { if (!CoopGame::cellFilled(livePiece, cx, cy)) continue; - float px = sf.targetX + static_cast(cx) * sf.tileSize; - float py = sf.targetY + static_cast(cy) * sf.tileSize; + int pyIdx = livePiece.y + cy; + if (!pinToFirstVisibleRow && pyIdx < 0) continue; + float px = baseX + static_cast(cx) * sf.tileSize; + float py = baseY + static_cast(cy) * sf.tileSize; drawBlockTexturePublic(renderer, blocksTex, px, py, sf.tileSize, livePiece.type); } } if (blocksTex) SDL_SetTextureAlphaMod(blocksTex, 255); - // Critical: only deactivate once the real piece is actually drawable. - // Otherwise (notably for I spawning at y=-2) there can be a brief gap where - // the fade ends but the real piece is still fully above the visible grid. - const bool pieceHasAnyVisibleCell = (livePiece.y + minCy) >= 0; - if (t >= 1.0f && pieceHasAnyVisibleCell) { + // End fade after duration, but never stop while we are pinning (otherwise + // I can briefly disappear until it becomes visible in the real grid). + if (t >= 1.0f && !pinToFirstVisibleRow) { sf.active = false; } }; @@ -2436,9 +2457,9 @@ void GameRenderer::renderCoopPlayingState( }; const auto leftOffsets = computeOffsets(CoopGame::PlayerSide::Left, s_leftSmooth); const auto rightOffsets = computeOffsets(CoopGame::PlayerSide::Right, s_rightSmooth); - // Draw transient spawn fades (if active) into the first visible row - drawSpawnFadeIfActive(s_leftSpawnFade, CoopGame::PlayerSide::Left); - drawSpawnFadeIfActive(s_rightSpawnFade, CoopGame::PlayerSide::Right); + // Draw transient spawn fades (if active) + drawSpawnFadeIfActive(s_leftSpawnFade, CoopGame::PlayerSide::Left, leftOffsets); + drawSpawnFadeIfActive(s_rightSpawnFade, CoopGame::PlayerSide::Right, rightOffsets); // Draw classic-style ghost pieces (landing position), grid-aligned. // This intentionally does NOT use smoothing offsets.