Files
spacetris/Spacetris-COOPERATE Mode.md

5.8 KiB
Raw Blame History

Spacetris — COOPERATE Mode

Middle SYNC Line Effect (SDL3)

VS Code Copilot AI Agent Integration Guide

Goal: Implement a visual SYNC divider line in COOPERATE mode that communicates cooperation state between two players.
This effect must be lightweight, performant, and decoupled from gameplay logic.


1. Concept Overview

The SYNC Line is the vertical divider between the two halves of the COOPERATE grid.

It must:

  • Always be visible
  • React when one side is ready
  • Pulse when both sides are ready
  • Flash on line clear
  • Provide instant, text-free feedback

No shaders. No textures. SDL3 only.


2. Visual States

Define these states:

enum class SyncState {
    Idle,        // no side ready
    LeftReady,   // left half complete
    RightReady,  // right half complete
    Synced,      // both halves complete
    ClearFlash   // row cleared
};

3. Color Language

State Color Meaning
Idle Blue Neutral
Left / Right Ready Yellow Waiting for partner
Synced Green (pulsing) Perfect cooperation
ClearFlash White Successful clear

4. Geometry

The SYNC line is a simple rectangle:

SDL_FRect syncLine;
syncLine.x = gridCenterX - 2;
syncLine.y = gridTopY;
syncLine.w = 4;
syncLine.h = gridHeight;

5. Rendering Rules

  • Always render every frame
  • Use alpha blending
  • Pulse alpha when synced
  • Flash briefly on clear

6. SyncLineRenderer Class (Required)

Header

#pragma once
#include <SDL3/SDL.h>

enum class SyncState {
    Idle,
    LeftReady,
    RightReady,
    Synced,
    ClearFlash
};

class SyncLineRenderer {
public:
    SyncLineRenderer();

    void SetRect(const SDL_FRect& rect);
    void SetState(SyncState state);
    void TriggerClearFlash();

    void Update(float deltaTime);
    void Render(SDL_Renderer* renderer);

private:
    SDL_FRect m_rect;
    SyncState m_state;

    float m_flashTimer;
    float m_time;

    static constexpr float FLASH_DURATION = 0.15f;

    SDL_Color GetBaseColor() const;
};

Implementation

#include "SyncLineRenderer.h"
#include <cmath>

SyncLineRenderer::SyncLineRenderer()
    : m_state(SyncState::Idle),
      m_flashTimer(0.0f),
      m_time(0.0f) {}

void SyncLineRenderer::SetRect(const SDL_FRect& rect) {
    m_rect = rect;
}

void SyncLineRenderer::SetState(SyncState state) {
    if (state != SyncState::ClearFlash)
        m_state = state;
}

void SyncLineRenderer::TriggerClearFlash() {
    m_state = SyncState::ClearFlash;
    m_flashTimer = FLASH_DURATION;
}

void SyncLineRenderer::Update(float deltaTime) {
    m_time += deltaTime;

    if (m_state == SyncState::ClearFlash) {
        m_flashTimer -= deltaTime;
        if (m_flashTimer <= 0.0f) {
            m_state = SyncState::Idle;
            m_flashTimer = 0.0f;
        }
    }
}

SDL_Color SyncLineRenderer::GetBaseColor() const {
    switch (m_state) {
        case SyncState::LeftReady:
        case SyncState::RightReady:
            return {255, 220, 100, 200}; // yellow

        case SyncState::Synced:
            return {100, 255, 120, 220}; // green

        case SyncState::ClearFlash:
            return {255, 255, 255, 255}; // white

        default:
            return {80, 180, 255, 180};  // idle blue
    }
}

void SyncLineRenderer::Render(SDL_Renderer* renderer) {
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

    SDL_Color color = GetBaseColor();

    if (m_state == SyncState::Synced) {
        float pulse = 0.5f + 0.5f * std::sinf(m_time * 6.0f);
        color.a = static_cast<Uint8>(160 + pulse * 80);
    }

    SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
    SDL_RenderFillRect(renderer, &m_rect);

    if (m_state == SyncState::ClearFlash) {
        SDL_FRect glow = m_rect;
        glow.x -= 3;
        glow.w += 6;

        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 180);
        SDL_RenderFillRect(renderer, &glow);
    }
}

7. Integration Flow

Initialization

SyncLineRenderer syncLine;
syncLine.SetRect({
    gridCenterX - 2.0f,
    gridTopY,
    4.0f,
    gridHeight
});

Update Loop

syncLine.Update(deltaTime);

Game Logic → Sync State Mapping

if (leftRowReady && rightRowReady)
    syncLine.SetState(SyncState::Synced);
else if (leftRowReady)
    syncLine.SetState(SyncState::LeftReady);
else if (rightRowReady)
    syncLine.SetState(SyncState::RightReady);
else
    syncLine.SetState(SyncState::Idle);

On Line Clear Event

syncLine.TriggerClearFlash();

Render Loop

syncLine.Render(renderer);

8. Performance Requirements

  • ≤ 2 draw calls per frame
  • No textures
  • No dynamic allocations
  • No blocking logic
  • Suitable for 60240 FPS

9. UX Rationale

  • Color communicates state without text
  • Pulse indicates readiness
  • Flash provides satisfaction on success
  • Reinforces cooperation visually

10. Acceptance Criteria

  • Divider is always visible
  • State changes are instantly readable
  • Flash triggers exactly on cooperative line clear
  • No performance impact
  • Clean separation from gameplay logic

11. Optional Future Enhancements (Not Required)

  • Vertical energy particles
  • Sound hooks on SYNC / CLEAR
  • Combo-based intensity
  • Color-blind palette
  • Gradient or glow variants

Summary for Copilot

Implement a SyncLineRenderer class in SDL3 to render the cooperative divider line. The line reflects cooperation state through color, pulse, and flash effects. The renderer must be lightweight, stateless with gameplay, and fully driven by external state updates.