Why this page exists
“Just read the code and find something to fix” is terrible onboarding advice for a pre-production project. This page gives you a defined ramp: understand the rules, prove your understanding through tests, then expand outward. Every task here is designed to be completable without running Unity at all.Key responsibilities
Every first-contribution task on this page serves one of three outcomes:Philosophy: start in the Domain, prove with tests
The Domain layer is pure C# — noUnityEngine, no Nakama, no R3. It compiles and runs in any .NET environment. This means you can write and run unit tests from Unity’s EditMode test runner in seconds, without entering Play mode.
Starting here serves two purposes:
- You learn the game’s core logic by implementing it, not just reading about it.
- You leave behind a test suite that protects everyone who comes after you.
GameSession in the Application layer is doing — and why the reactive streams flowing into Presentation look the way they do.
Where your first tasks sit in the architecture
Your first tasks live entirely insideSET.Domain (green). The Application layer is read-only study material for now. Infrastructure and Presentation are out of scope until Domain and Application are solid.
Starter tasks
1
Read the game rules and domain model docs
Before writing code, understand what you’re modelling. Read:
/game-design/rules— the complete unambiguous SET rules, including the all-same-or-all-different attribute check, board expansion logic, and win conditions./core-gameplay/card-model— the domain model document coveringCard,CardAttributes,Board,Match, and the identity scheme.
2
Implement SetValidator — pure C#, TDD
SetValidator is a stateless domain service that answers one question: are these three cards a valid Set? It is the most important class in the project and the natural place to start.Create Assets/_Project/Domain/Services/SetValidator.cs implementing ISetValidator:Assets/_Tests/EditMode/SetValidatorTests.cs and cover these cases before touching the implementation:- All four attributes all-same → valid
- All four attributes all-different → valid
- Three cards where one attribute has two of one value and one of another → invalid
FindAllSetson a 12-card board returns the expected number of SetsAnySetExistsreturnsfalsewhen no valid Set is present
SetValidator is also used server-side as the authoritative implementation — keep it deterministic and allocation-light.3
Write tests for Deck
Deck encapsulates the 81-card collection and its draw logic. Create tests in SetValidatorTests.cs or a separate DeckTests.cs:- Shuffle with a fixed seed produces a deterministic card order (run twice, compare results)
- Draw N cards removes exactly N cards from the deck
- Draw from an empty deck returns an empty result without throwing
- No card appears more than once across the full 81-card deck (verify the complete set of
CardIdvalues)
CardId computed as:4
Write tests for Board
Board manages the active card grid. Write tests in BoardTests.cs:- Place a card in an empty slot — slot reports occupied
- Remove a card from an occupied slot — slot reports empty
- Board starts with 12 cards (4 × 3 grid) in standard configuration
- Board expands to 15 cards when no Set is present (3 new slots added)
- Board cannot expand beyond 21 cards (7 × 3 maximum)
- Removing a Set from a full-expansion board contracts back to 12 if enough cards remain
CardSlot can hold exactly zero or one Card. Placing a second card in an occupied slot should return an error result, not throw.5
Read the Interface Contracts doc
Once your Domain tests pass, read
/architecture/layers and the interface contracts document (if available) to understand how GameSession in the Application layer consumes ISetValidator and IAIScanner. You’ll see that GameSession is instantiated with these interfaces injected via constructor — it never calls new SetValidator() directly.This prepares you for the next step without requiring you to change anything.6
Explore the GameSession stub in Application
Look at
Assets/_Project/Application/Sessions/GameSession.cs. You’re not modifying it yet — you’re reading it to understand:- How it receives
ISetValidatorandIAIScannervia constructor injection - How it manages the
MatchStateenum transitions - How it emits state and events as R3 observables through
IGameStateProvider
GameSession never calls new SetValidator() or new AIScanner() directly — VContainer supplies the concrete implementations through constructor injection at startup.When you’re ready to contribute Application-layer work, start by adding test coverage for GameSession state transitions using NSubstitute mocks for ISetValidator and IAIScanner.Key types you’ll work with
Card attributes and enums
The four domain enums define every possible card attribute value:CardAttributes bundles them into a single immutable value object:
ISetValidator interface
SetResult struct
SetResult is returned instead of throwing exceptions. Domain errors are values, not exceptions.
Test naming convention
All test method names follow the patternMethodName_Scenario_ExpectedBehavior:
Implementation checklist
Before you mark a pull request ready for review, confirm every item below:- All new and existing EditMode tests pass (run via Window → General → Test Runner in Unity)
- No
using UnityEngine;anywhere inSET.DomainorSET.Application - No
using Nakama;anywhere inSET.DomainorSET.Application - No
using R3;anywhere inSET.Domain - Test methods follow the
MethodName_Scenario_ExpectedBehaviornaming convention -
SetResultreturned for invalid input; no exceptions thrown from domain services - Value objects (
CardAttributes,SetResult) are immutable (readonly struct) -
SetValidatoris stateless — no instance fields mutated between calls -
Deckshuffle with a fixed seed is deterministic (tested with two identical runs) -
Boardexpansion and contraction rules tested at boundary values (12, 15, 18, 21 cards)
Common mistakes
Related pages
Repo Tour
Understand the folder hierarchy and assembly boundaries before you start editing.
Card Model
The full domain model — Card, Board, Match aggregate root, and CardId scheme.
Set Validation
Deep dive into the SetValidator algorithm and edge cases.
Engineering Standards
Naming conventions, formatting rules, and the complete PR checklist.