Crystal's Debut

Winner — Pirate Jam 18

Project Type
Pirate Jam 18
Date
Jan 2026
Status
In Development
itch.io Page
Play Crystal's Debut Demo
Engine
Unity Engine
Roles
Project Manager, Lead Programmer, Lead Game Designer

The player plays as Crystal, a newly hired streamer and follows requests from chat. Requests include interacting with the computer on the screen to complete actions to raise her approval meter. The actions range from minigames, interacting with applications on the computer, to messing with Crystal’s pet Gerbil Kevin.

Introduction

Crystal's Debut was developed for Pirate Jam 18, drawing inspiration from Needy Streamer Overload and a shared passion for VTuber culture. The project blends charming minigames and endearing characters with an undercurrent of corporate pressure and eldritch interference — a deliberate contrast that gives the game its tone.

The goal was to create something mechanically approachable but narratively layered, where every viewer request feels consequential and Crystal's world slowly reveals itself to be stranger than it first appears.

Dialogue & Request System — Yarn Spinner 3

To handle Crystal's chat requests and main dialogue, I integrated Yarn Spinner 3 — a narrative scripting tool built for Unity that allows dialogue to be written in a clean, readable format and driven at runtime without hardcoding conversation logic into C#.

The primary challenge was learning Yarn Spinner's node and command architecture from scratch and designing a system where dialogue could trigger game actions seamlessly. To bridge the gap between narrative and gameplay, I wrote a suite of custom C# commands registered with Yarn Spinner's command dispatcher. These commands allowed Yarn scripts to directly invoke request logic — launching minigames, updating the approval meter, and interacting with in-game objects — without any manual intervention from other systems.

This approach kept all request sequencing and branching inside the Yarn scripts themselves, making it straightforward to add, reorder, or adjust requests without touching the underlying C# codebase.

A key design challenge was distinguishing between Crystal's main dialogue and messages delivered through her in-game messaging app. Rather than building a separate system, I extended the existing Yarn Spinner integration by tagging specific lines with #message directly in the Yarn scripts. A custom dialogue presenter listens for this tag at runtime and routes those lines into the messaging app UI instead of the standard dialogue display — keeping all conversation logic in one place while allowing the presentation layer to vary depending on context.

Graze mechanic demo

Wordle Minigame Breakdown

One of the minigames embedded in Crystal's Debut is a fully functional Wordle clone, playable directly on Crystal's in-game computer. The game pulls from two word lists loaded at runtime — a common solutions list and a broader valid words list — and selects a random target word each time the minigame is triggered.

The trickiest part of the implementation was the two-pass letter evaluation system in OnRowSubmit(). A naive single-pass approach produces incorrect results when the guess contains duplicate letters — for example, guessing "APPLE" when the answer is "CRANE" could incorrectly mark both P's as wrong-spot rather than incorrect. To handle this accurately, the evaluation runs in two passes. The first pass checks for exact matches, marking any correct letters and removing them from a tracked remaining string. The second pass then checks the leftover letters for wrong-spot or incorrect states, consuming each match from remaining as it goes. This ensures duplicate letters are accounted for precisely and never double-counted.

Wordle minigame demo

// Pass 1 — exact matches
for (int i = 0; i < row.tiles.Length; i++)
{
    WordleTile tile = row.tiles[i];

    if (tile.letter == targetWord[i])
    {
        tile.SetState(correctState);
        remaining = remaining.Remove(i, 1).Insert(i, " ");
    }
}

// Pass 2 — wrong spot or incorrect
for (int i = 0; i < row.tiles.Length; i++)
{
    WordleTile tile = row.tiles[i];

    if (tile.state == correctState) continue;

    if (remaining.Contains(tile.letter))
    {
        tile.SetState(wrongSpotState);
        int index = remaining.IndexOf(tile.letter);
        remaining = remaining.Remove(index, 1).Insert(index, " ");
    }
    else
    {
        tile.SetState(incorrectState);
    }
}