//

> SYSTEM BOOT COMPLETE
> USER: Solus Bian
> LOCATION: Projects Archive
> STATUS: Experimenting with creative ideas in search of my next internship orbit

INTRODUCTION

Hello, I am Solus Bian. I'm a developer, and I create art and illustrations—two skill sets that don't typically share the same brain in this industry. I create projects that built entirely on passion and free time. I spend my time creating interactive experiences that blend code with creativity—games that range from silly experiments to darker, more introspective explorations, and experiments that exist purely because they seemed interesting at 2 AM. Every project here is something I genuinely enjoy working on, whether it's a solo effort or something I've hosted and led from concept to completion. I thrive in the intersection of structured development and creative exploration.This site is where all of that lives. It's not a polished showcase—it's a working archive, and in many ways, a reflection of my emotions and creative state at different moments. Current projects sit alongside abandoned experiments, finished games next to half-formed ideas. Much of this website is built purely with CSS, HTML, and JavaScript—handcrafted pieces that make up the whole. Think of it as a digital workshop: messy, functional, and constantly evolving. Browse the projects, check out what I'm building now, or just see what happens when artistry meets technical execution. You're already here, might as well look around.

https://sundog.rip

[ Feel free to interact with the embedded site on the left (desktop) or below (mobile), or view it directly at this link: sundog.rip. ]

Sundog.rip. My personal freelance website, active for over 10 years. I started taking monetary commissions for artwork from strangers in 2017—back in middle school. What began as a way to make some money doing what I enjoyed has evolved into a daily practice. I complete commissions daily and weekly for over multiple years, balancing client work with personal projects. This site is where all of that happens: terms of service, commission information & rates, and the process for working together. You can also sign up for my commission update and newsletter.

MindLog

const scrollBtn = document.getElementById('scrollToTop'); window.addEventListener('scroll', function() { if (window.pageYOffset > 300) { scrollBtn.classList.add('visible'); } else { scrollBtn.classList.remove('visible'); } }); scrollBtn.addEventListener('click', function() { window.scrollTo({ top: 0, behavior: 'smooth' }); });

> ACTIVE PROJECT LOGS
> Real-time development entries. Current builds, bugs encountered, and progress updates. Scroll within container to view full entry.

entry 006:
to be published ...

entry 005: Snake Collector Game
>02-12-2026
After the core systems were working with placeholder rectangles, started the art pass. Created a visual novel-style intro with oStartMan that handles 5 background sprites playing once then idling (alternating last 2 frames every 0.2s for a "breathing" effect), typewriter text at 2 chars/frame, and click-to-advance system. The tricky part was detecting animation end with imageindex >= imagenumber - 1, locking to last frame with imagespeed = 0, then manually alternating frames for the idle loop. Added a pulsing "Click to continue" using sin(currenttime / 200) for smooth alpha. After page 5, auto-transitions to battle with roomgotonext().
Battle Visual Polish
Upgraded all battle elements to proper sprites. Cards are now 180x80 with a face/back system: faceSprite stores the actual card sprite (sBasicAttack, etc.), while spriteindex switches to sCard when in deck. Critical detail: must save face sprite BEFORE setting to card back, or it's lost. Positioned deck in bottom-right corner with 10px Y-offset stacking (targetY = decky + i * 10) to create visual depth. Hand cards fan from screen center using roomwidth/2 - (handSize * handxoffset)/2 + i * handxoffset with 100px offset for nice overlap. Added hover tooltips above cards showing descriptions in black boxes with wrapped text using drawtextext(). Player HP is now an 800x50 bar (red background, green fill based on hp percent, centered text) that overlays the background sprite since the player character is baked into the background art.
Animation Systems
Snake sprites got frame-based animation: manual counter increments, checks ranges (0-4 idle, 5-9 attack), image
index = currentFrame. Attack damage triggers mid-animation at frame 7 using alarm[0] = animationSpeed * 2 for perfect timing punch. Energy dice flash rapidly during isAnimating = true (cycle through all 4 colors every 3 frames) then lock to final result after 60 frames. When hovering cards, energy highlights in two passes: first loop grabs matching colors, second loop fills remaining with white wildcards. All buttons got uniform polish: lerp to 110% scale on hover, 3px offset shadow at 0.3 alpha, scale to 90% on click, text scales with button using drawtexttransformed(). Dark screen (0.85 alpha) appears during energy rolling to focus attention.
Technical Wins & Next Steps
The biggest "aha" was separating hover counter (0-10) from boolean - creates smooth transitions perfect for lerping. Two-pass energy consumption (colored then white) stays clean by looping twice instead of complex nested logic. Storing isAnimating separately from state prevents bugs when you're in ROLLING phase but waiting for player input. Current sprite sizes: cards 180x80, energy dice ~40x40, HP bar 800x50, stars sprite sheet with 9 frames for half-star increments. Game is now playable and looks real - next phase is map polish, better car sprite, task system juice, sound effects, and particle effects for damage numbers. Most time went into card positioning math for centered hands with variable counts. ~8-11 hours total for intro + battle art pass.ShareArtifactsDownload allDevlog 01 foundation dayDocument · MD Devlog 02 art and polishDocument · MD Orating review systemGML Otask card systemGML Otaskman call systemGML Omapman grass systemGML Rmmap driving systemGML Ostartman completeGML Snake animation quickGML Snake animation fixedGML End turn quick fixGML Fixed end turn cycleGML All buttons with hoverGML End turn hoverGML Implementation summaryGML Oman alarm 0GML Oman draw 0GML Ocard step 0GML Osnake other 10GML Osnake alarm 0GML Three fixes referenceGML Fixed highlighting and rerollGML Reroll dark screen systemGML Implementation guideGML Project contentsnakeGameCreated by youpdf

entry 004: Snack Collector Game
> 02-11-2026
Started development on a snake-catching card battler. In one marathon session (~8-10 hours), built the entire battle system from scratch: energy dice rolling with reroll mechanics, a deck-building card system with prioritized energy consumption (colored first, then white wildcards), and a complete turn-based state machine. Implemented smooth animations everywhere using lerp - dice flash through colors for 1 second before landing, cards slide from deck to hand, buttons scale on hover. The trickiest bug was dice rerolling ALL instead of just selected ones, fixed by adding if(isAnimating) checks on individual dice. Created 12 objects including card manager, energy manager, snake with frame-based attack animations (frames 0-4 idle, 5-9 attack), and a player HP system with defense/reflect/buff mechanics.
Technical Highlights
State machines carried everything. Energy system: ROLLING → WAITING → LOCKED. Battle flow: START (roll dice, dark screen) → DEAL (cards dealt) → PLAYERTURN → SNAKETURN → loop. Used GameMaker's alarm system for turn transitions and dslist for all dynamic collections (deck, hand, discard, energy). Two-click card system: first click highlights required energy with color priority logic, second click consumes and plays. Snake animations trigger damage mid-attack at frame 7 using alarm[0] = animationSpeed * 2 for perfect timing. All buttons got hover effects (scale to 110%, shadow on hover, scale to 90% on click) with lerp smoothing at 0.2 speed.
After getting battles working perfectly, moved to the map portion. Created a 4000x5000 room with WASD-controlled car and camera that follows and rotates with the car using camera
setviewangle(cam, -imageangle). But here's the problem: the car rotates, the camera rotates to match, but pressing W drives in the car's world-space direction, not screen-space. I want it like a racing game where forward is ALWAYS up on screen. The camera also rotates around its top-left corner instead of its center, so the car drifts off-center when rotating. Tried calculating offsets with lengthdirx/y based on camera dimensions and angle, but the math isn't clicking yet. The battle system is solid and fun - just need to crack this camera centering to complete the core game loop.

Contact Informations:

Reach me via email: [email protected] for personal matters
  or [email protected] for work matters

Nope Rope Collection

NOPE ROPE
COLLECTION

Catch Snakes. Play Cards. Save Lives.

You're a professional rattlesnake removal specialist. When panicked citizens call about dangerous serpents, you drive to their location and engage in turn-based card battles against increasingly deadly snakes. Roll energy dice, build your deck, and prove you're the best in the business—or get a 1-star review.

🎲

Energy Dice

Roll colored energy dice each turn. Red for attacks, blue for defense, yellow for buffs, and rare white wildcards. Reroll strategically!

🃏

Deck Building

Play cards using rolled energy. Deal damage, block attacks, reflect damage, or debuff enemies. Draw new cards each turn.

🚗

Drive & Respond

Accept emergency calls, drive across town, and battle unique snakes. Complete tasks quickly for 5-star reviews!

Rating System

Win fast: "Amazing service!" Lose: "The snake ate my child..." Never show up: "1 star. Bad."

⏱️

Timed Missions

Each task has a 3-minute timer. Accept multiple calls and juggle priorities. Choose wisely!

🐍

Boss Battles

The Pentagon calls: "There's a GIANT snake!" Face off against massive serpents with devastating power.

Works & Projects

G R I E F

A journey through the five stages of grief, expressed through art, code, and interactive media

Stage One — Grief: Denial

Color Palette Art Collection

An exploration of emotional restraint through minimalist color theory. This collection examines the quiet refusal to accept reality, translating the numbness of denial into stark chromatic contrasts. Each piece strips away complexity until only essential hues remain—the absence of color (white), the void (black), and the violent intrusion of red that cannot be ignored.

THEMATIC PALETTE

White
#F3F4F5
Gray
#DDDDDD
Black
#242024
Red
#BF0000
Red (Highlight)
#E30220

ARTISTIC APPROACH

The work explores whump themes through visual metaphor—the body under duress, emotional containment, the moment before breaking. White represents the blank slate of shock. Gray embodies the fog of incomprehension. Black is the abyss we refuse to look into. And red... red is the truth that bleeds through.

Stage Two — Grief: Anger

Incendiary

A Story Video Game About Burning It All Down

The Setup

You wake in your living room. Today has been a masterclass in loss: laid off from your job, you attended your dog's funeral this morning—flashes of a tiny coffin, your dog's favorite squeaky toy resting on top. You're in that perfect storm of grief and incandescent rage when you hear a knock. A chipper mail carrier stands on your stoop, clipboard in hand, delivering an official eviction notice. Your home is being demolished to make way for "a luxury community." You have 30 days.

Game Objective

Navigate your rage through increasingly destructive choices. Set everything on fire—metaphorically or literally. Find all three endings. Will you succumb to violence, channel your fury into resistance, or find an uncomfortable peace? The game tracks your choices, building toward three distinct narrative conclusions that explore the boundaries of justified anger.

Your First Choice

01. Burn the mail in the kitchen sink, watch the paper curl and blacken, let the smoke alarm scream
02. Attack the messenger—a man with a wife and three kids whose photos you'll never see ACHIEVEMENT: UR A MONSTER
03. Accept the mail, close the door, begin the slow calcification of compliance ACHIEVEMENT: GOOD CITIZEN

DESIGN PHILOSOPHY: This game asks: when does righteous anger become destructive? When is destruction justified? Each playthrough reveals new layers of moral complexity, forcing players to confront their own capacity for violence when pushed to the edge.

Stage Three — Grief: Bargaining

BARGAINING

Creative Coding Project

An algorithmic exploration of negotiation with the inevitable. This generative art piece uses procedural generation to visualize the bargaining mind—the frantic mental calculations, the "what if" scenarios playing out in infinite variation. Code becomes the language of desperate mathematics.

function bargain(reality, desired_outcome) {
  while (reality !== desired_outcome) {
    attempt_negotiation();
    recalculate_possibilities();
    offer_compromise();
  }
}
// infinite loop inevitable

The project generates endless permutations of "if only" statements, rendered as visual patterns that never quite resolve. Users can input their own bargains, watching the algorithm attempt impossible negotiations with fate. The code always fails. That's the point.

Built with p5.js, exploring recursive functions, probability trees, and the beautiful futility of trying to code your way out of grief.

Grief: Depression & Acceptance

FINAL STAGES

Depression and Acceptance — the weight and the release

Stage Four — Grief: Depression

NYX

A short animated film following Nyx's descent into grief after losing his brother Irie to drowning—a tragedy made worse by the fact that Irie died trying to save him. This isn't a story about moving on quickly. It's about the weight of survivor's guilt, the blur between memory and hallucination, the way grief distorts reality itself.

The animation captures depression's disorienting nature through visual metaphor. Nyx sees Irie everywhere—greeting him at the door, embracing him in hallways. Reality fractures. The house becomes surreal, dimensions parallel and overlapping. He can't tell what's real anymore. The beach where it happened haunts him. The storm that took Irie never really ends in his mind.

Set to "Dreams Pt. II" by Lost Sky and Sara Skinner, the film moves through Nyx's emotional landscape: joyful flashbacks of brothers playing at the beach, the gradual darkening as memory shifts to tragedy, the storm sequence where past and present collide. But it also shows the slow, painful climb toward acceptance—Nyx placing white flowers on the beach at sunrise, learning to live with the weight, finding peace not in forgetting but in remembering without drowning.

Medium: 2D animatic with tweening | Song: Dreams Pt. II by Lost Sky & Sara Skinner | Runtime: ~2:00 | Themes: Survivor's guilt, grief hallucinations, the journey from drowning in sorrow to surfacing

Stage Five — Grief: Acceptance

Emotion Detection System

A machine learning model trained to recognize and classify human emotional states from facial expressions and vocal patterns. But unlike typical sentiment analysis, this system acknowledges the complexity of acceptance—that final stage where multiple emotions coexist without contradiction.

The model doesn't just detect "happy" or "sad." It recognizes the subtle expression of someone who is both grieving and at peace. It identifies the micro-expressions of someone who has stopped fighting reality.

TRAINING DATA 10,000+ labeled images of genuine emotional states, focusing on complex expressions that traditional models miss
ARCHITECTURE Convolutional neural network with attention mechanisms to detect subtle facial muscle movements
OUTPUT Multi-label classification allowing simultaneous emotions: "acceptance + sadness," "peace + grief," "resolution + loss"
APPLICATION Understanding that acceptance isn't the absence of pain—it's the presence of peace alongside pain
AI Hearts Player - Header
Machine Learning Project

AI HEARTS PLAYER

Autonomous Card Game Agent Using GPT-4 & Selenium

Shanghai Jiao Tong University

An intelligent agent that plays the card game Hearts by combining web automation with large language model reasoning. The system uses Selenium to interact with an online Hearts game, extracts real-time game state information, and leverages GPT-4 to make strategic decisions based on game theory and optimal play patterns.

AI Hearts Player - Body

System Architecture

Game State Detection

  • Web Scraping: Selenium WebDriver extracts card positions, game messages, and UI elements
  • State Machine: Tracks game phases (STARTING, PASSING, WAITING, PLAYING, GAME_END)
  • Card Recognition: Parses CSS classes to identify cards by suit and rank
  • Position Tracking: Uses element coordinates to distinguish hand cards from played cards

AI Decision Engine

  • GPT-4 Integration: Sends structured prompts with complete game state
  • Rule-Based Prompting: Embeds Hearts strategy rules into LLM context
  • Fallback Logic: Basic heuristics when AI response is invalid
  • Action Execution: Translates AI decisions into mouse clicks via ActionChains

Strategic Decision Framework

Core Objective

Minimize point accumulation (Hearts = 1 point each, Queen of Spades = 13 points)

Avoid taking the Queen of Spades unless attempting to "shoot the moon"

Shooting the moon (taking all hearts + Queen) gives 26 points to all opponents

Leading a Trick (First to Play)

Play lowest non-heart card to avoid winning later tricks

If only hearts remain, lead with lowest heart

Following Suit (Responding to Play)

Must follow suit if possible

Avoid winning tricks containing hearts or Queen of Spades

In late game or when shooting the moon, play highest to win trick

Sloughing (Cannot Follow Suit)

Priority #1: Dump Queen of Spades if held

Priority #2: Discard highest heart to shed points

Priority #3: Discard high non-heart cards

Implementation Details

Game State Extraction

# Identify cards in hand vs. played cards y_positions = [card.location['y'] for card in face_up_cards] your_y = max(set(y_positions), key=y_positions.count) in_hand_cards = [card for card in face_up_cards if abs(card.location['y'] - your_y) <= 5] # Determine trick leader (first card played) trickcard = min(played_thisround, key=lambda c: (c['element'].location['x'], c['element'].location['y']))

Position-based heuristics differentiate hand cards from cards in play

AI Prompt Structure

prompt = f"""You are playing Hearts. Current state: Round: {round} Your Hand: {[card['value'] for card in hand]} Leading Trick: {trickcard['value'] if trickcard else "you are leading"} Played This Round: {[c['value'] for c in played_thisround]} Completed Tricks: {[trick['value'] for trick in trick_cards_sorted]} Remaining Cards: {card_existing} [Strategic Rules...] Return ONLY the card to play (e.g., 'h2' or 's14')."""

Structured prompt provides complete game context to GPT-4 for optimal decision-making

Technology Stack

Selenium WebDriverBrowser automation and DOM interaction
GPT-4 (OpenAI)Strategic decision-making engine
Python 3Core implementation language
ActionChainsPrecise mouse movement and clicking
State MachineGame phase management
ChromeDriverChrome browser automation

Project Outcomes

Successfully demonstrated the integration of large language models with real-time game environments. The system autonomously plays complete games of Hearts, making strategic decisions that balance risk avoidance with point minimization. The project showcases how AI can be applied to complex decision-making scenarios involving incomplete information and probabilistic reasoning.

Deckora
Strategic Card Game

DECKORA [DEMO]

Every Decision Matters. Choose Wisely.

A strategic card battler that challenges your decision-making skills in every round. Each turn, you'll be presented with three cards, but you can only choose one—whether to attack, defend, or upgrade your ability. The abilities of these cards remain constant throughout the battle, so careful selection and timing are key to outmaneuvering your opponent. With each decision, you come closer to victory or defeat, making every move crucial in this high-stakes game of strategy.

Core Mechanics

The Three-Card Choice

Every turn presents you with three options, but you can only choose one. This fundamental constraint forces strategic thinking—do you prioritize offense, defense, or long-term advantage? The abilities remain constant throughout the battle, meaning mastery comes from understanding when to use each card, not what the cards do. It's a game of perfect information and imperfect choices.

Attack
Opponent

Deals 100% of player damage to the opponent. This card will be deflected if the opponent casts the deflect card, making timing crucial to maximize damage output.

Deflect
Attack

Deals 50% of the opponent's damage back to them. Only activates if the opponent plays an attack card, rewarding prediction and defensive play.

Upgrade
Yourself

Increases player damage by 100% and heals the player by 50%. A risky investment that pays dividends over time but leaves you vulnerable in the current turn.

Monster Encounters

Aggressive Type

Attack: 10HP: 20Upgrade: 5

Defensive Type

Attack: 2HP: 30Upgrade: 8

Strategic Depth

Risk vs. Reward

Deckora's elegance lies in its simplicity. With only three choices, the game becomes a psychological battle of prediction and adaptation. Upgrading early can snowball into overwhelming advantage, but leaves you vulnerable to aggressive opponents. Deflecting at the wrong time wastes a turn. Attacking when your opponent deflects hands them free damage. Every decision ripples through the entire match, creating a tense dance of strategy where one wrong move can cost you the game.

Development

EngineGameMaker
PlatformHTML5 / Web Browser
GenreCard Game / Fighting
StatusReleased
Still Breathing
Visual Art Collection

STILL BREATHING

Endurance, Response, Survival

Human struggle and suffering are universal experiences—no one lives in a perfect world or a perfect life. Everyone faces challenges, big or small. The following works reflect these realities and explore how people endure, respond, and survive.

This collection examines the human condition through the lens of pain, perseverance, and the quiet act of continuing to exist. Each piece confronts the weight of living—the moments when survival itself becomes an act of defiance, when breathing is the only victory left.

Thematic Exploration