[{"data":1,"prerenderedAt":626},["ShallowReactive",2],{"portfolio-general":3,"portfolio-game-design":370},{"variant":4,"profile":31,"experience":58,"projects":139,"skills":197,"writings":257,"generatedAt":369},{"key":5,"label":6,"headline":7,"tagline":8,"heroHeading":9,"heroProject":10,"heroAccent":11,"heroSub":12,"accent":13,"sectionOrder":14,"featuredTags":21,"projectGroups":22,"projectsHeading":26,"resumeUrl":27,"ctaPrimary":28,"availability":29,"aboutHtml":30},"general","Software","Software Engineer","Software engineer in Orlando. Web applications, developer tools, and games.","Hi, I'm Christian.","formship",null,"I'm a software engineer in Orlando. I build web applications, developer tools, and games.","accent-1",[15,16,17,18,19,20],"hero","projects","experience","about","writings","skills",[],[23],{"type":24,"label":25},"app","Selected work","Selected projects","\u002Fresume\u002FChristian-DAlbano-Resume.pdf","Explore projects","Open to software and gameplay engineering roles, Orlando or remote","\u003Cp>My work spans web applications, integrations, and data pipelines. At Mouseflow, I’ve built analytics interfaces and integrations. At Cencora, I build Python tools for production data workflows and maintain their deployment pipelines.\u003C\u002Fp>\n\u003Cp>Outside work, I build my own products and make games. Formship helps coaches put together learning programs, and HasteCV is a resume builder I use myself. Making games gives me room to draw, experiment with gameplay, and see how people respond to what I’ve made.\u003C\u002Fp>\n",{"name":32,"headline":7,"location":33,"email":34,"site":35,"socials":36,"bioHtml":57},"Christian D'Albano","Orlando, FL","chrisdalbano12@gmail.com","chrisdalbano.com",[37,41,45,49,53],{"label":38,"url":39,"icon":40},"GitHub","https:\u002F\u002Fgithub.com\u002Fchrisdalbano","github",{"label":42,"url":43,"icon":44},"LinkedIn","https:\u002F\u002Flinkedin.com\u002Fin\u002Fchrisdalb","linkedin",{"label":46,"url":47,"icon":48},"itch.io","https:\u002F\u002Fchrisdbgames.itch.io","itch",{"label":50,"url":51,"icon":52},"Wavedash","https:\u002F\u002Fwavedash.com\u002Fchrisdalbano12","waves",{"label":54,"url":55,"icon":56},"Email","mailto:chrisdalbano12@gmail.com","mail","\u003Cp>Software engineer in Orlando. I build web applications, developer tools, and games.\u003C\u002Fp>\n",[59,80,105,121],{"slug":60,"title":61,"org":62,"start":63,"end":64,"location":33,"tags":65,"variants":73,"order":74,"bullets":75},"cencora","Senior Data Operations Engineer","Cencora (Intrinsiq)","2025-07","present",[66,67,68,69,70,71,72],"python","sql-server","azure-databricks","azure-devops","ci-cd","docker","data-quality",[5],1,[76,77,78,79],"Build Python tooling that monitors and manages high-volume data pipelines on Azure Databricks and SQL Server.","Own CI\u002FCD for production deployments through Azure DevOps, adding pre-deploy checks that cut rollout incidents.","Designed the diagnostics and alerting layer for the data workflows that serve practices, providers, and data sources across the network.","Work with engineering and analytics on the consumer-facing Vue surfaces that sit on top of the pipeline.",{"slug":81,"title":7,"org":82,"start":83,"end":64,"location":84,"tags":85,"variants":98,"order":99,"bullets":100},"mouseflow","Mouseflow","2023-08","Remote",[86,87,88,89,90,91,92,93,94,95,96,97],"vue","blazor","javascript","chart-js","web-sdk","integrations","shopify","wordpress","optimizely","salesforce","agentic-ai","mcp",[5],2,[101,102,103,104],"Led the migration of the core analytics dashboard from jQuery to Vue 3 and Blazor, retiring legacy code and speeding up customer-facing views.","Built the dashboard’s data-visualization components in Vue and Chart.js: heatmaps, session-replay surfaces, and funnel views used across customer dashboards.","Owned the integrations platform end to end (Shopify, WordPress, Optimizely, Convert, Salesforce, and the customer JavaScript SDK), taking each connector through discovery, build, QA, and release.","Build and run production agentic-AI workflows with Claude Code, Cursor, MCP servers, and custom agents that the team uses for development and integration work.",{"slug":106,"title":107,"org":108,"start":109,"end":110,"location":84,"tags":111,"variants":115,"order":116,"bullets":117},"aizhak-coffee","Full-Stack Developer (Contract)","Aizhak Coffee","2023-09","2024-01",[86,112,113,71,114],"typescript","firebase","ecommerce",[5],3,[118,119,120],"Built a real-time ecommerce storefront in Vue 3 and TypeScript on Firebase, from cart to live inventory.","Shipped the MVP ordering flow in under six weeks, working directly with design and backend.","Launched it live at \u003Ca href=\"http:\u002F\u002Faizhak.com\">aizhak.com\u003C\u002Fa>.",{"slug":122,"title":123,"org":124,"start":125,"end":83,"location":126,"tags":127,"variants":133,"order":134,"bullets":135},"baptist-health","Junior Frontend Developer","Baptist Health South Florida","2022-06","Miami, FL",[128,112,129,130,131,132],"react","java-spring-boot","accessibility","wcag","healthcare",[5],4,[136,137,138],"Built responsive patient-portal interfaces in React and TypeScript under WCAG accessibility requirements.","Integrated the frontend with Java Spring Boot APIs handling sensitive patient data.","Cut UI load time through code-splitting and bundling.",[140,156,171,184],{"slug":10,"name":141,"blurb":142,"contribution":143,"url":144,"repo":11,"type":24,"tags":145,"variants":150,"featured":152,"order":74,"cover":153,"wavedash":11,"engine":11,"jam":11,"media":154,"descriptionHtml":155},"Formship","A platform for coaches to create learning programs, with editable content blocks and AI-generated quizzes.","Product design and full-stack development","https:\u002F\u002Fformship.io",[146,147,96,148,86,66,149],"saas","ai","ai-orchestration","education",[5,151],"game-design",true,"\u002Fshots\u002Fformship.webp",[],"\u003Cp>Formship helps coaches turn their frameworks into structured learning programs. I designed and built the product myself in Vue and Python.\u003C\u002Fp>\n\u003Ch2>Generating content coaches can use\u003C\u002Fh2>\n\u003Cp>Content generation runs in steps, with each step producing structured data for the next. That gives the app quizzes it can render and score, and gives coaches content they can edit before sharing it with learners.\u003C\u002Fp>\n\u003Ch2>Building around content blocks\u003C\u002Fh2>\n\u003Cp>Programs are made from modular blocks. Coaches can rearrange them, the generation pipeline can create them, and the analytics layer tracks learner progress against them. Keeping that structure consistent made it easier to connect authoring, generation, and learning in the same product.\u003C\u002Fp>\n",{"slug":157,"name":158,"blurb":159,"contribution":160,"url":161,"repo":11,"type":24,"tags":162,"variants":167,"featured":152,"order":99,"cover":168,"wavedash":11,"engine":11,"jam":11,"media":169,"descriptionHtml":170},"buildvalue","BuildValue","Compare League of Legends items and full builds by their gold efficiency, using data from Riot's API.","Full-stack development and item-data pipeline","https:\u002F\u002Fbuildvalue.chrisdalbano.com\u002F",[163,164,86,165,66,166,89],"league-of-legends","full-stack","fastapi","data-pipeline",[5,151],"\u002Fshots\u002Fbuildvalue.webp",[],"\u003Cp>BuildValue helps League of Legends players compare the stats they get for their gold. It calculates item efficiency from the prices of basic stats and lets players compare complete builds.\u003C\u002Fp>\n\u003Cp>The numbers are a starting point: active abilities, passives, and the timing of a purchase also affect an item’s value. The interface makes the stat comparison available while leaving room for that judgment.\u003C\u002Fp>\n\u003Cp>I built it with Vue and Chart.js, backed by FastAPI and MongoDB. A scheduled Python pipeline refreshes the item data from Riot’s DDragon API.\u003C\u002Fp>\n",{"slug":172,"name":173,"blurb":174,"contribution":175,"url":176,"repo":11,"type":24,"tags":177,"variants":180,"featured":152,"order":99,"cover":181,"wavedash":11,"engine":11,"jam":11,"media":182,"descriptionHtml":183},"hastecv","HasteCV","A resume and cover-letter builder with rich-text editing, PDF exports, and an MCP server for AI clients.","Product design, full-stack development, and MCP API","https:\u002F\u002Fhastecv.com",[146,147,97,164,178,86,179],"django","tiptap",[5,151],"\u002Fshots\u002Fhastecv.webp",[],"\u003Cp>HasteCV is a resume and cover-letter builder I designed and built myself. It uses Django for the API and Vue with Tiptap for editing. Background jobs handle generation and exports.\u003C\u002Fp>\n\u003Ch2>Documents that people and tools can edit\u003C\u002Fh2>\n\u003Cp>Each document has structured fields for templates and exports, alongside a rich-text body for editing. Through the MCP server, AI clients can read and edit documents or export them as PDFs. It also exposes the document schemas so clients can check the required fields before writing.\u003C\u002Fp>\n\u003Cp>The API returns readable errors, and archiving is the default removal action. Those choices make mistakes easier to understand and recover from.\u003C\u002Fp>\n\u003Ch2>Using it myself\u003C\u002Fh2>\n\u003Cp>I use the server in my own application workflow to prepare documents and export PDFs. That gives me a regular way to test whether the editing and export tools work together as intended.\u003C\u002Fp>\n",{"slug":185,"name":108,"blurb":186,"contribution":187,"url":188,"repo":11,"type":24,"tags":189,"variants":192,"featured":193,"order":116,"cover":194,"wavedash":11,"engine":11,"jam":11,"media":195,"descriptionHtml":196},"aizhak-ecommerce","A coffee storefront with a shopping cart, live inventory, and online ordering.","Storefront development and launch","https:\u002F\u002Faizhak.com",[114,86,190,113,191],"node","real-time",[5],false,"\u002Fshots\u002Faizhak-ecommerce.webp",[],"\u003Cp>I led development of the Aizhak Coffee storefront and shipped its first version. The Vue frontend connects to Node services and Firebase for inventory and orders. I worked from Figma designs to build the shopping and checkout experience.\u003C\u002Fp>\n",[198,207,217,229,240,249],{"label":199,"items":200,"variants":206},"Languages",[201,202,203,204,205],"TypeScript","JavaScript","Python","SQL","GDScript",[5,151],{"label":208,"items":209,"variants":216},"Frontend",[210,211,212,213,214,215],"Vue 3","React","Blazor","Tailwind","Chart.js","Tiptap",[5,151],{"label":218,"items":219,"variants":228},"Backend & Data",[220,221,222,223,224,225,226,227],"Node.js","Django","DRF","Flask","Java Spring Boot","SQL Server","Azure Databricks","Firebase",[5],{"label":230,"items":231,"variants":239},"AI & Agentic",[232,233,234,235,236,237,238],"Claude Code","Claude API","Cursor","MCP","Multi-agent orchestration","Prompt engineering","Structured output",[5,151],{"label":241,"items":242,"variants":248},"Game Dev",[243,244,205,245,246,247],"Phaser 3","Godot 4","Krita","Hand-painted art","Data-driven design",[5,151],{"label":250,"items":251,"variants":256},"Infra & Ops",[252,253,254,255],"Docker","Azure DevOps CI\u002FCD","Git","Render",[5],[258,272,284,298,309,323,334,342,352,360],{"slug":259,"title":260,"dek":261,"date":262,"project":263,"tags":264,"variants":268,"order":269,"readingMinutes":270,"contentHtml":271},"i-wont-be-abducted-after-the-jam","I Won't Be Abducted: fixing what players couldn't read","A placement flow that passed its tests and ate clicks, a boss with bad instructions, and an economy that gave tools away. The September update.","2026-09-07","i-wont-be-abducted",[265,266,151,267],"postmortem","godot","usability",[5,151],0,5,"\u003Cp>The crafting menu opened. The tool went into your pocket. Then you clicked the board to place it, and nothing happened.\u003C\u002Fp>\n\u003Cp>That was the first bug I chased after \u003Cem>I Won’t Be Abducted\u003C\u002Fem> shipped, and it set the tone for the whole September update. The others were quieter. Crafting worked but gave you far too much. The boss had a weakness and did a bad job of showing it. Adding a fourth night and more systems had exposed how much the game expected you to work out while aliens were hitting you.\u003C\u002Fp>\n\u003Cp>The \u003Ca href=\"\u002Fnotes\u002Fi-wont-be-abducted-postmortem?variant=game-design\">jam postmortem\u003C\u002Fa> covers the weekend build and its cuts. This is about what changed afterward. Repeated playtests decided what needed another pass. The game now runs four nights, with a bigger room on the last two. Making those systems make sense took more work than adding them.\u003C\u002Fp>\n\u003Ch2>A passing test, a broken click\u003C\u002Fh2>\n\u003Cp>The placement bug was annoying because the placement logic was tested. Call the function directly and it worked. Click through the actual interface and it did not.\u003C\u002Fp>\n\u003Cp>Decorative full-screen layers were swallowing pointer input before the board ever saw it. The test skipped the exact part that was broken. The regression check had to click, in the viewport, on the actual pocket button, instead of asking the placement function whether a cell was valid.\u003C\u002Fp>\n\u003Cp>There was a design problem underneath the bug. Picking a recipe, finding its pocket, and choosing a cell while a Rusher charged you asked for too much attention. Crafting and placement now pause the fight. Legal cells glow, an invalid cell shows a cross, and a blocked placement leaves the tool in your pocket instead of eating it.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fi-wont-be-abducted\u002Fplacement-2026-09.webp\" alt=\"Night four, paused: legal cells glow green while a tin can is placed beside the boy\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"900\">\n\u003C\u002Ffigure>\n\u003Cp>The workbench had to answer three things at once: what can I make, what does it cost, and what will I have left. Coin and item icons help. They only matter once the click underneath them is reliable.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fi-wont-be-abducted\u002Fworkbench-2026-09.webp\" alt=\"The workbench: three illustrated recipes with coin costs and a wallet forecast\" loading=\"lazy\" decoding=\"async\" width=\"1280\" height=\"720\">\n\u003C\u002Ffigure>\n\u003Ch2>The boss needed instructions on the board\u003C\u002Fh2>\n\u003Cp>The final boss had two opposite problems in a row. In the first expanded build, repeated kicks could end the fight almost immediately. Adding a guard and attack windows stopped that, and then nobody could tell when the opening was.\u003C\u002Fp>\n\u003Cp>The cues now sit above the boss: \u003Cstrong>WAIT\u003C\u002Fstrong>, \u003Cstrong>DODGE THIS ROW\u003C\u002Fstrong>, \u003Cstrong>KICK NOW\u003C\u002Fstrong>. The threatened row is marked before the slam. During an opening, the free cells next to him light up with lines pointing at him, and a small countdown shows how long you have. He takes one hit per opening, seven openings total, and standing in the marked row costs a heart.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fi-wont-be-abducted\u002Fopening-2026-09.webp\" alt=\"The boss's KICK NOW cue, its countdown, and the highlighted cells beside him\" loading=\"lazy\" decoding=\"async\" width=\"1280\" height=\"720\">\n\u003C\u002Ffigure>\n\u003Cp>The change that worked was putting the instruction where you were already looking. A paragraph in a popup is useless when you are trying to leave a row and line up a kick at the same time.\u003C\u002Fp>\n\u003Ch2>Crafting was practically free\u003C\u002Fh2>\n\u003Cp>Cheap tools, frequent coin drops, and money from tool-assisted kills all fed each other. The decoy was the worst offender. Distracting the whole board bought enough time to do everything else.\u003C\u002Fp>\n\u003Cp>Tools now cost six coins instead of three. Free coin drops come every thirty seconds instead of every twelve. Kills caused by bowling a tool into an alien no longer pay. And retrying a night restores its starting wallet and pockets, so a string of failed attempts cannot become a farm.\u003C\u002Fp>\n\u003Cp>The tools also needed clearer reasons to exist. The can bowls a crowd and bursts to stun. The box soaks five hits and shields a neighbouring cell from the boss’s slam. The decoy pulls crawlers and rushers away and dies after three bites. Those roles give me something concrete to tune. If distraction still wins every decision, adding another recipe is not the answer.\u003C\u002Fp>\n\u003Ch2>Keep the jokes out of the player’s way\u003C\u002Fh2>\n\u003Cp>Enemy speech bubbles fit a game about a kid turning his bedroom into a siege. They also compete with instructions. Only one short bubble shows at a time, with a gap between them, and any chatter clears before the boss’s warning.\u003C\u002Fp>\n\u003Cp>The visual changes follow the same restraint: faint paper grain, a soft vignette, dust drifting through the lamp light. The armoured alien’s bucket needed to look like part of the character rather than an icon stuck on top, and the loss screen now shows a toppled toy instead of a kid on the floor. The original drawings still set the look.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Ending spoilers:\u003C\u002Fstrong> letting Dad in used to drop you at the menu. Now there is a short conversation and credits, so the joke has time to land: the invasion interrupted lunch, and your defence system is a pencil box. After all that effort keeping him out, the game needed a moment with him in the room.\u003C\u002Fp>\n\u003Ch2>What I still need to watch\u003C\u002Fh2>\n\u003Cp>Automated boss runs prove the fight can be won. They cannot tell me whether a person understands the first opening, notices a coin cost, or finds an alien’s line funny while dodging it.\u003C\u002Fp>\n\u003Cp>So that is the next playtest: watch someone craft once and fight Dad without me explaining either system. If I have to lean over and say what to press, there is still work to do.\u003C\u002Fp>\n\u003Cp>\u003Ca href=\"https:\u002F\u002Fwavedash.com\u002Fgames\u002Fi-won-t-be-abducted\">Play I Won’t Be Abducted on Wavedash\u003C\u002Fa>.\u003C\u002Fp>\n",{"slug":273,"title":274,"dek":275,"date":262,"project":276,"tags":277,"variants":282,"order":74,"readingMinutes":134,"contentHtml":283},"silhouettes-before-commitments","Silhouettes before commitments","The four-gate look test I run before any character or prop earns a place in a game.","spare-knight",[278,279,280,281],"method","art-direction","character-design","production",[5,151],"\u003Cp>The most expensive art mistake I made this year was building a 3D hero before I had decided what he looked like. The model was competent. It was also the wrong character, and nothing about it survived once the design changed. Since then every character, prop, and world piece goes through the same four gates, cheapest first, and only leaves when it survives all of them.\u003C\u002Fp>\n\u003Cp>None of the images here is finished game art. The point of the method is that finished art is the last thing you make.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fprocess\u002Flook-test-loop.webp\" alt=\"The look-test loop: silhouettes, paint, build, in-engine, judge\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"720\">\n\u003C\u002Ffigure>\n\u003Ch2>Gate 1: six silhouettes, one pick\u003C\u002Fh2>\n\u003Cp>A silhouette is the cheapest possible version of a design, so it is where bad ideas should die. I ask for six directions, not six variations. Each gets a name and a one-line concept, and each has to be a different answer to the brief. “The Stooped Sentinel” and “The Bucket Helm” are different characters. Six antenna heights on one body are not, and when I got that back for the \u003Cem>ThunderBashers\u003C\u002Fem> robot I sent it back.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fspare-knight\u002Fcog-silhouettes.webp\" alt=\"Six Cog directions: Stooped Sentinel, Oathwall, Bellkeeper, Broken Standard, Iron Palm, Pilgrim\" loading=\"lazy\" decoding=\"async\" width=\"1536\" height=\"1024\">\n\u003C\u002Ffigure>\n\u003Cp>Every sheet includes a 64-pixel strip, because that is the size the character is on screen most of the time. Then I pick with written criteria and a score per direction: does it read as the thing it is in one glance, does it carry the emotional read the story needs, is it distinct from the generic version of its genre, and can a face live in it. Writing the criteria down does two things. It stops me choosing the prettiest drawing, and it gives the next pass a reason to argue with.\u003C\u002Fp>\n\u003Ch2>Gate 2: paint it next to its neighbour\u003C\u002Fh2>\n\u003Cp>The winner gets a turnaround and a hero sheet at game scale beside the character it will share the screen with. For Cog that meant standing him next to Wren, the human he protects. A design that looks good alone and wrong beside its companion fails here, and this is still cheap to fail.\u003C\u002Fp>\n\u003Cp>The paint gate also checks the style contract: ink weight, flat fills with one shadow tone, palette, warmth. If the design does not sit beside the game’s existing art, no amount of modelling will fix it. For Spare Knight this gate is where I take over with a brush, because the shipped characters are painted and the new one has to match them.\u003C\u002Fp>\n\u003Ch2>Gate 3: build it, then validate it with a script\u003C\u002Fh2>\n\u003Cp>Only now does the design become an asset: a low-poly model with the outline as geometry, feet on the origin, footprint under one grid cell, a face budget. I stopped checking these by eye. A validation script imports each file, measures it, and prints a pass or fail per rule. Arguments about “it looks about right” in the viewport ended the day the script existed.\u003C\u002Fp>\n\u003Cp>For a 2D game with a sprite rig the build gate is different but the principle holds: render from the game camera into the exact parts the rig expects, and validate the pivots against what the engine generates rather than what the export tool claims. That is how the \u003Cem>ThunderBashers\u003C\u002Fem> robots went from a model back into a seven-sprite rig within three percent of the original bounds.\u003C\u002Fp>\n\u003Ch2>Gate 4: in the engine, or it didn’t happen\u003C\u002Fh2>\n\u003Cp>A render is not proof. Renders lie about scale, about how glow reads under real lighting, and about what the camera occludes. The only screenshot that counts is a live capture from the engine with the shipped art beside the candidate. Three times this year a 3D character that looked good on a turntable lost to a painted one under the real light. Each time the loss was obvious in the engine and invisible in the render.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fspare-knight\u002Fcog-3d-look-tests.webp\" alt=\"Look tests: the earlier 3D Cog beside the reworked one, same light and camera\" loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"830\">\n\u003C\u002Ffigure>\n\u003Ch2>The judgement\u003C\u002Fh2>\n\u003Cp>Every pass ends the same way: a scorecard with a number per gate, a verdict of keep, partial keep, or drop, and three concrete changes for the next pass. The three changes are the important part. They turn a rejection into a brief, and they mean the next pass starts from evidence instead of taste. The Cog redesign took three passes. The first produced a competent model of the wrong character. The second produced a better model of the same wrong character. The third started at gate 1 again, with a proper inspiration list, and produced a design worth painting.\u003C\u002Fp>\n\u003Ch2>What I’d keep\u003C\u002Fh2>\n\u003Cp>Kill ideas at the cheapest gate. Never skip the paint gate for a character, even when the model is the deliverable. Replace visual checks with a validator wherever a rule can be measured. And treat the three changes at the end of a pass as the real output. The asset is just evidence that the brief worked.\u003C\u002Fp>\n",{"slug":285,"title":286,"dek":287,"date":288,"project":289,"tags":290,"variants":295,"order":74,"readingMinutes":296,"contentHtml":297},"undead-signal-postmortem","Undead Signal: five days of rules before a line of code","Killing three drafts, the rule that gave back depth I never designed, and balancing a roguelike with a bot that plays 300 runs.","2026-07-27","undead-signal",[265,291,292,151,293,294],"unity","jam","balancing","card-game",[5,151],6,"\u003Cp>I made \u003Cem>Undead Signal\u003C\u002Fem> for a Wavedash jam with the theme \u003Cstrong>Bad Connection\u003C\u002Fstrong>. It is an anti-poker zombie roguelike, live on Wavedash as a WebGL build with achievements, leaderboards, and cloud saves. It was my fourth shipped game and my second in Unity, after \u003Cem>Spare Knight\u003C\u002Fem> in Phaser, \u003Cem>I Won’t Be Abducted\u003C\u002Fem> in Godot, and \u003Cem>ThunderBashers\u003C\u002Fem>.\u003C\u002Fp>\n\u003Cp>This one was design-first on purpose. The earlier jams taught me to ship. This time I wanted the rules to be the interesting part, so I spent the first five days entirely on rules, in a design vault, before any build existed.\u003C\u002Fp>\n\u003Ch2>The pitch\u003C\u002Fh2>\n\u003Cp>You are a radio engineer restoring the signal in overrun locations. The dead are networked by frequency. A lone zombie is a body, but when they align into a lineup, a poker hand, they form a resonance that hits you at the location’s exit. Each act deals more zombies onto the table. Whatever you do not remove survives to form the horde’s best five-card hand, and its threat comes out of your health.\u003C\u002Fp>\n\u003Cp>Threat is one equation: the sum of the five zombie ranks times a poker-hand multiplier. That gives you exactly two levers. Drop the sum by killing or shrinking the big ranks, or break the structure by scattering a pair and splitting a straight. Breaking their hand is fixing the bad connection is restoring the signal. The theme and the core mechanic are the same sentence, which is the thing in the whole design I am proudest of.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fsystems\u002Fundead-signal-systems.svg\" alt=\"Systems map: the threat equation, the two levers, the price of every verb, and the run loop\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"900\">\n\u003C\u002Ffigure>\n\u003Ch2>Killing three drafts\u003C\u002Fh2>\n\u003Cp>The honest history is that the first drafts were derivative. Draft one was a straight zombie card battler. Draft two leaned hard into Balatro: build your hand, stack multipliers, score big. It functioned on paper and it was boring on paper, because Balatro already exists and a worse Balatro with zombies is a reskin, not a game.\u003C\u002Fp>\n\u003Cp>The unlock was inverting it. In Balatro you assemble the scoring hand. What if the enemy assembles it, and every verb you have subtracts from their hand instead of adding to yours? The power fantasy flips from building a combo to dismantling one. The poker knowledge players already carry becomes threat literacy instead of a scoring manual. And the zombie fiction snaps into place: the horde is the hand.\u003C\u002Fp>\n\u003Cp>There was a third draft in between, a corridor-crawl hybrid, and it died too. I kept every dead draft in an archive folder instead of deleting it. If you do solo design work, do this. The graveyard is what proves the final rule set was chosen rather than stumbled into. Five days of it felt irresponsible mid-jam. It was the best production decision in the project, because the build phase never had to stop and redesign anything.\u003C\u002Fp>\n\u003Ch2>The rule that gave back\u003C\u002Fh2>\n\u003Cp>My favourite moment in systems design is when one rule starts producing depth I did not put there. In \u003Cem>Undead Signal\u003C\u002Fem>, a zombie’s rank is also its health. Hit it for less than its rank and it does not die, it shrinks. A Mutated 9 hit for 3 becomes a 6.\u003C\u002Fp>\n\u003Cp>Which means shrinking can backfire. If two 6s are already on the table, you just completed three of a kind and raised the threat you were trying to lower. That interaction was never designed. It fell out of “rank is HP” colliding with “poker hands multiply,” and the first time it happened in testing I knew the ruleset was alive. I kept it, the game tells you to read the board, and it is the rule people quote back to me most.\u003C\u002Fp>\n\u003Ch2>Balancing with a bot\u003C\u002Fh2>\n\u003Cp>The other half of the jam was learning what balancing means when you are solo and the game is a twenty-five minute run. You cannot feel-test your way to a win rate. So the game got a balance lab: a headless harness where a threat-aware bot plays 300 seeded runs per configuration and prints the funnel. Win rate, clear rate per location, how people die.\u003C\u002Fp>\n\u003Cp>The first measurement was humbling. Win rate: 19 percent. If the bot used the game’s own loot-digging verb naively, it dropped to 5 percent, which meant one of my core verbs was a trap. The numbers found root causes I would never have felt: whole runs passing without a usable weapon before the first exit, and an average first-location exit damage of 60 against 100 health. The deadliest room in the game was the front door.\u003C\u002Fp>\n\u003Cp>Two dial changes later, gear density up and a rule guaranteeing a weapon in the first few cards, the win rate hit 31 percent against a 30 to 40 target, and deaths moved to the late-game wall where they belong. Then playtesting produced the opposite complaint: hands full at all times, loot stopped mattering. So weapons got durability, failed kills got bite-back, and the lab re-tuned the whole thing to 37 percent with weapons cycling. Every one of those numbers is a logged report I can re-run.\u003C\u002Fp>\n\u003Cp>The meta-lesson: the lab also measured the dial I thought I understood. Gear density turned out to be two dials in one, because more gear also means fewer zombies in the deck, and the second effect was the stronger one. By hand I would have tuned that dial wrong forever.\u003C\u002Fp>\n\u003Ch2>Shipping on a platform\u003C\u002Fh2>\n\u003Cp>This was my first release on Wavedash rather than a plain web upload, and the SDK work was its own small discipline: ten achievements, two leaderboards, the player’s name on the title screen, cloud saves for settings and lifetime stats. The game has no native score, so the leaderboards run on a derived composite, progress first and kills as the tiebreaker, that had to be designed like any other rule. Launch day was a rhythm of build, push, patch against live reports. Five updates went out on day one. It felt closer to live ops than to jam submission, and I want more of it.\u003C\u002Fp>\n\u003Ch2>Since then\u003C\u002Fh2>\n\u003Cp>The September update, the Survivor’s Table, rebuilt the interface around one idea: answer the player’s question where they are already looking. Drag a weapon onto a card and the forecast appears beside the target, kill or shrink, bite-back, threat before and after. Right-click any card for a full-art dossier. Hover a keyword and read its rule. It is the same game with the rules finally visible, which is the ThunderBashers lesson again: readability is a mechanic.\u003C\u002Fp>\n\u003Ch2>What I’d keep\u003C\u002Fh2>\n\u003Cp>Spend the design days before the build days, and keep the dead drafts as the receipt. When a rule produces depth you did not author, that is the ruleset telling you it works. And balance is a measurement problem. A bot that plays 300 runs will find the truth about your economy faster than a hundred feel passes, and it leaves you numbers you can defend later.\u003C\u002Fp>\n",{"slug":299,"title":300,"dek":301,"date":302,"project":303,"tags":304,"variants":307,"order":74,"readingMinutes":270,"contentHtml":308},"thunderbashers-postmortem","ThunderBashers: shipping a Unity brawler in five days","A mid-jam engine switch, a mechanic that felt broken for three days, and the telegraph that fixed it.","2026-06-27","thunderbashers",[265,291,292,305,306],"game-feel","bot-ai",[5,151],"\u003Cp>I made \u003Cem>ThunderBashers\u003C\u002Fem> in about five days for the Very Serious Juniper Dev Game Jam. The theme was \u003Cstrong>Spin To Win\u003C\u002Fstrong>. I did the design, the direction, the art, and the audio pass myself, and it went up on \u003Ca href=\"http:\u002F\u002Fitch.io\">itch.io\u003C\u002Fa> as a WebGL build with a Windows download for anyone whose browser choked. It was my third jam game and my first in Unity, after \u003Cem>Spare Knight\u003C\u002Fem> in Phaser and \u003Cem>I Won’t Be Abducted\u003C\u002Fem> in Godot.\u003C\u002Fp>\n\u003Ch2>The pitch\u003C\u002Fh2>\n\u003Cp>Robots in a circular pit bash a storm-charged ball at each other. Spinning is the only thing you do: it hits the ball, it parries a hit coming at you, and it shoves a rival who gets too close. If the ball touches you while you are not spinning, you are out. Last robot standing wins. I did not want the theme to be a coat of paint on a brawler. Spinning is the offense, the defense, and the win condition.\u003C\u002Fp>\n\u003Cp>The wrapper is a deadpan corporate league, the THUNDERBASHERS PRO LEAGUE, whose Standards Committee insists the rules are fair. On a jam that rewards “serious,” the joke was free points, and it gave every bot a reason to have a name.\u003C\u002Fp>\n\u003Cp>The first sketch already had the whole game on it. The five days were about the distance between that sketch and a build that felt like it.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fthunderbashers\u002Fconcept-characters.webp\" alt=\"Early concept sketch: the robots, the spin, and the ball\" loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"600\">\n\u003C\u002Ffigure>\n\u003Ch2>The pivot\u003C\u002Fh2>\n\u003Cp>I started in Unreal. Half a day in, the engine was spending my time instead of saving it: no clean path to WebGL, slow iteration, and every hour in the editor was an hour not drawing robots. I moved to Unity with about five days left.\u003C\u002Fp>\n\u003Cp>That forfeited the “Best Unreal Engine Game” prize, and I knew it when I did it. In exchange, the game plays instantly in a browser on the itch page, which means more plays and more ratings, and it opened a “Best Web Game” target. The Unreal start is parked for a hands-on project later. Changing engines mid-jam sounds reckless. The real risk was shipping nothing, and the switch is what protected shipping.\u003C\u002Fp>\n\u003Ch2>The mechanic that fought back\u003C\u002Fh2>\n\u003Cp>“The ball kills you if it catches you not spinning” was supposed to be the easy part. It was the hardest thing in the game. For three days it felt wrong, and the temptation every evening was to tune the rule again.\u003C\u002Fp>\n\u003Cp>The controls were simple from the first hour: move, aim with the mouse, space to spin. Simple to operate and good to play turned out to be different problems.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fthunderbashers\u002Fconcept-controls.webp\" alt=\"Early controls sketch: move, mouse to aim, space to spin\" loading=\"lazy\" decoding=\"async\" width=\"400\" height=\"400\">\n\u003C\u002Ffigure>\n\u003Cp>When I stopped chasing symptoms and went looking for the cause, “the mechanic is broken” turned out to be five separate problems stacked on top of each other. Any touch killing you made parrying feel like a timing puzzle bolted onto a brawl. Over-correcting to a speed gate meant enemies almost never died. The ball kept parking in a speed band that read as neither safe nor lethal. One value was gating a bot’s offense and its defense at once, so tuning a bot to be beatable also made it stop fighting. And the ball could look red-hot while the kill check briefly read it as safe after a wall bounce.\u003C\u002Fp>\n\u003Cp>Every fix that “didn’t work” was fixing a real bug. Just not the one in front of the player at that moment.\u003C\u002Fp>\n\u003Ch2>Readability is a mechanic\u003C\u002Fh2>\n\u003Cp>What finally made the core feel right was not a rules change. It was a heat tint on the ball: cool blue when it is slow and harmless, ramping to red and crackling when it is fast enough to kill. Once you could see the threat state, “a slow ball doesn’t hurt you” read as obvious instead of buggy. The biggest feel win in the game came from legibility.\u003C\u002Fp>\n\u003Cp>That is the lesson I kept. When a mechanic feels wrong, it is often a readability problem wearing a rules problem’s clothes. Fix what the player can see before you change what the game does.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fthunderbashers\u002Fheat-ball.webp\" alt=\"The thunder-ball glowing red-hot: the danger telegraph\" loading=\"lazy\" decoding=\"async\" width=\"1343\" height=\"635\">\n\u003C\u002Ffigure>\n\u003Ch2>Bots with temperaments\u003C\u002Fh2>\n\u003Cp>It is a single-player game, so the bots are the game. I did not want a difficulty slider. I wanted opponents with personalities. Every bot is a data profile: reaction time, aim error, spin skill, how far ahead it looks for an incoming ball, how readily it panics. The profiles fall into five archetypes, from a rookie who mistimes everything and feeds you confidence kills to a veteran you only beat with wall ricochets and baited parries.\u003C\u002Fp>\n\u003Cp>Two bugs taught me the most. Bots crowded the ball and multi-killed in one pass, which looked stupid; the fix was electing one “committed striker” each frame so they approach one at a time. And splitting offense from defense, so a bot always strikes but parries by skill, was what let me tune a bot to be beatable without it going passive. The campaign then distributes those archetypes across three arenas to build the curve.\u003C\u002Fp>\n\u003Ch2>What I’d keep\u003C\u002Fh2>\n\u003Cp>Root-cause, don’t symptom-chase. Build to your real target early, because the shipped build is the only build that counts. And readability is a mechanic: the most legible version of a rule usually feels like the best-designed one. A small, finished, legible game beats a big unfinished one every time the clock is running.\u003C\u002Fp>\n",{"slug":310,"title":311,"dek":312,"date":313,"project":11,"tags":314,"variants":321,"order":74,"readingMinutes":116,"contentHtml":322},"building-this-site","This site is the case study","The design tokens and motion system behind the previous version of this portfolio.","2026-06-11",[315,316,317,318,319,320],"design-systems","nuxt","tokens","motion","ai-tooling","process",[5,151],"\u003Cp>The site you are reading is the most recent thing I shipped, so it should answer the question a portfolio usually dodges: what does this person’s work look like when nobody scoped it for him? I rebuilt \u003Ca href=\"http:\u002F\u002Fchrisdalbano.com\">chrisdalbano.com\u003C\u002Fa> from scratch in about two and a half weeks of nights and weekends, solo, from first scaffold to DNS cutover. Not from a template. The constraint I set on day one: every visual decision has to live in a system, not in a component.\u003C\u002Fp>\n\u003Ch2>Tokens before pixels\u003C\u002Fh2>\n\u003Cp>The styling is built on a three-layer token architecture: primitives (raw oklch values, type sizes, durations), semantic tokens (surface, ink, accent roles), and component tokens that map semantics onto anatomy. Components are only allowed to consume the top layer. There is a binding rule in the repo doctrine: no hex literals in components, ever. Tailwind v4 reads the same tokens through \u003Ccode>@theme\u003C\u002Fcode>, so CSS and utility classes draw from one source.\u003C\u002Fp>\n\u003Cp>That rule is what makes the site’s party trick cheap. There are two versions of this portfolio, a software engineering identity and a game design identity, and they are the same components fed different data and a swapped accent role. Switching variants costs one data attribute. No forked components, no second stylesheet.\u003C\u002Fp>\n\u003Ch2>Motion as vocabulary, not decoration\u003C\u002Fh2>\n\u003Cp>Animation runs through a finite set of eleven named presets (motion-v under the hood): entrances, emphasis, and the hero treatments. Finite is the point. When every animation comes from a shared vocabulary with shared easings and durations, the page feels composed instead of assembled. Every preset carries an explicit reduced-motion branch, and a global guard backs it up, because respecting \u003Ccode>prefers-reduced-motion\u003C\u002Fcode> is table stakes for calling something crafted.\u003C\u002Fp>\n\u003Ch2>Content modeled like a CMS, without the CMS\u003C\u002Fh2>\n\u003Cp>The content is not in the components. It is authored as markdown with typed frontmatter in a private vault, and a build script bakes it into per-variant JSON the site renders from. Each content item declares which identities it belongs to; the pipeline emits one payload per variant. That separation is the same shape as a headless CMS: content modeling, an editorial workflow, and a render layer that never touches the source. It also means publishing a new project or note is a markdown commit, not a deploy decision.\u003C\u002Fp>\n\u003Ch2>The two-agent workflow\u003C\u002Fh2>\n\u003Cp>The part I would defend hardest: how it was built. I run two Claude Code agents with deliberately separated powers. A design-authority agent owns tokens, grid, component anatomy, and motion specs, and writes them to a specs directory. An engineering agent implements those specs in Vue and Tailwind and is forbidden from making design decisions. The spec file is the handoff contract. If a spec is ambiguous, the engineer sends it back rather than improvising.\u003C\u002Fp>\n\u003Cp>This sounds like ceremony for a personal site. It is the opposite. Solo builders drift; the system held the line on every late-night shortcut I was tempted to take, and it is why the site stayed coherent while shipping fast. It is also a working answer to a question I care about professionally: what does AI-assisted craft look like when you architect the collaboration instead of just prompting harder?\u003C\u002Fp>\n\u003Cp>The repo doctrine, token files, and motion presets are all real artifacts, not retrofitted documentation. If you are reading this as a hiring signal: this is the process you would be hiring.\u003C\u002Fp>\n",{"slug":324,"title":325,"dek":326,"date":327,"project":263,"tags":328,"variants":332,"order":74,"readingMinutes":116,"contentHtml":333},"tuning-fear-by-numbers","Tuning fear by numbers","The player's kit never changes. Every bit of difficulty in I Won't Be Abducted lives in a Godot resource, and here is how that held up.","2026-06-09",[329,266,330,331],"systems-design","difficulty","data-driven",[5,151],"\u003Cp>Most jam games escalate by addition. Night two adds an enemy, night three adds a weapon, and by the deadline you are debugging four mechanics instead of finishing one. For \u003Cem>I Won’t Be Abducted\u003C\u002Fem> I made the opposite bet before writing any code: the player’s kit stays constant for the whole game, and difficulty comes from the world.\u003C\u002Fp>\n\u003Cp>The boy you control on the last night is the boy from night one. Move cooldown 0.18 seconds, kick cooldown 0.4 seconds, three Nerve hearts. The dawn draft lets you pick one upgrade between nights, but that is your choice, not the curve. The curve never touches your hands.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fsystems\u002Fi-wont-be-abducted-systems.svg\" alt=\"Systems map: the fixed player config, the per-night dials, and the event bus\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"900\">\n\u003C\u002Ffigure>\n\u003Ch2>Where the curve lives\u003C\u002Fh2>\n\u003Cp>Every gameplay number sits in a Godot \u003Ccode>Resource\u003C\u002Fcode>, a \u003Ccode>.tres\u003C\u002Fcode> file you edit in the inspector without opening a script. Four resource types drive the game: \u003Ccode>PlayerConfig\u003C\u002Fcode>, \u003Ccode>EnemyData\u003C\u002Fcode>, \u003Ccode>AugmentData\u003C\u002Fcode>, and the one doing the difficulty work, \u003Ccode>NightConfig\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>duration\u003C\u002Fcode> (how long until dawn)\u003C\u002Fli>\n\u003Cli>\u003Ccode>spawn_interval\u003C\u002Fcode> (seconds between alien drops)\u003C\u002Fli>\n\u003Cli>\u003Ccode>max_on_board\u003C\u002Fcode> (population cap)\u003C\u002Fli>\n\u003Cli>\u003Ccode>enemy_pool\u003C\u002Fcode> (which enemies can spawn, with per-enemy weights)\u003C\u002Fli>\n\u003Cli>\u003Ccode>enemy_speed_scale\u003C\u002Fcode> (a multiplier on alien step timers)\u003C\u002Fli>\n\u003Cli>\u003Ccode>intensity_ramp\u003C\u002Fcode> (how spawn pressure climbs within a night)\u003C\u002Fli>\n\u003Cli>\u003Ccode>gift_interval\u003C\u002Fcode> \u002F \u003Ccode>gift_amount\u003C\u002Fcode> (Scrap pickups)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Night one and night four are the same scene, the same code, the same rules. They differ in those numbers, in which enemies are in the pool, and since September in the size of the board. Rebalancing means opening a file and turning a dial: make night one gentler by raising the spawn interval and lowering the cap. In the last hours of a 63-hour jam that mattered more than any feature. Tuning time is the scarcest resource a jam has, and a curve made of inspector fields is the cheapest curve to iterate.\u003C\u002Fp>\n\u003Ch2>When a number can’t fix it, the rule is wrong\u003C\u002Fh2>\n\u003Cp>Twice during the jam no dial produced the right feel, and both times the fix was a rule change rather than a bigger number.\u003C\u002Fp>\n\u003Cp>First: aliens only leave the board through real exits. The door and two windows are specific cells, not abstract directions. Players learned fast to hug corners, where a kick had no exit lane and did nothing. No dial fixes a wasted kick. A rule did: a kick that slams a chain of aliens into a wall now stuns the whole chain. Every kick does something. With kicks reliably useful, the population numbers came back down, night two from five aliens to four and night three from six to five.\u003C\u002Fp>\n\u003Cp>Second: the Lobber’s ray-gun dealt direct Nerve damage at range, which on a fifteen-cell board read as unfair because there was nowhere honest to hide. Changing its damage just moved the unfairness around. The rule change: the first ray-gun hit stuns you, and only a hit while you are already stunned costs a heart. Same enemy, same range, but the threat is readable and the punishment takes two mistakes.\u003C\u002Fp>\n\u003Cp>That became my working test for the whole project. Tune with numbers until the numbers stop helping, then change one rule, then go back to numbers.\u003C\u002Fp>\n\u003Ch2>What it bought after the jam\u003C\u002Fh2>\n\u003Cp>The September update was the real test of the approach. A fourth night, a bigger board, a new armoured alien, three craftable tools, and a boss with a seven-opening pattern all went in without touching the player’s kit. The new enemy is mostly a spawn weight in the night three and four pools plus one rule, a charged kick to launch him. The boss timings are inspector fields, so the “too easy, then unreadable” swing described in \u003Ca href=\"\u002Fnotes\u002Fi-wont-be-abducted-after-the-jam\">the follow-up postmortem\u003C\u002Fa> was tuned in data, not rewritten in code.\u003C\u002Fp>\n\u003Cp>The difficulty story also stays legible to the player, because nothing new is introduced mid-run. The game gets harder the way a board game gets harder when you add pieces: same rules, turned up.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fi-wont-be-abducted\u002Fpreview-chars.webp\" alt=\"Character standees from I Won't Be Abducted\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"900\">\n\u003C\u002Ffigure>\n",{"slug":335,"title":336,"dek":337,"date":262,"project":303,"tags":338,"variants":340,"order":99,"readingMinutes":296,"contentHtml":341},"thunderbashers-visual-overhaul","ThunderBashers: rebuilding the look without breaking the game","How the post-jam overhaul went out as version 1.1 on Wavedash: staged by visibility, guarded by flags, judged in the engine.",[265,279,339,281,291,305],"ui",[5,151],"\u003Cp>\u003Cem>ThunderBashers\u003C\u002Fem> shipped in five days with hand-drawn robots, a grid for a floor, a star field for a sky, and a UI assembled from code. The spin mechanic worked and the bots had temperaments. It looked like a jam game, because it was one. In September I gave it a full visual and feel overhaul and published it as version 1.1 on Wavedash. Two rules held for the whole thing: the shipped build had to stay playable at every commit, and every stage had to be judged in the engine, not in a mock-up.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fprocess\u002Fthunderbashers-stages.webp\" alt=\"The five stages of the overhaul, ordered by visibility per hour and risk\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"700\">\n\u003C\u002Ffigure>\n\u003Ch2>Order by visibility per hour\u003C\u002Fh2>\n\u003Cp>The first question was what to touch first. I ranked candidates by how much a player would notice per hour of work, weighed against how much gameplay code each one put at risk.\u003C\u002Fp>\n\u003Cp>Identity won. A logo, a menu, a UI kit, and a ball that reads as a storm sphere change the first thirty seconds of the game and touch almost no rules. Robots came second because they are what you look at while you play. The arena frame was third, since it is the backdrop for everything else. Animation and VFX went last on purpose. They depend on the final robot and the final palette, and tuning them against placeholders would have meant doing them twice.\u003C\u002Fp>\n\u003Ch2>Stage 1: the game gets a name\u003C\u002Fh2>\n\u003Cp>The tone already existed in the jam copy: “VERY SERIOUS Robot Combat League,” deadpan broadcast graphics for a sport that is robots hitting a lightning ball. The logo had to carry that joke. I ran three directions, a wordmark with a spinning-blade monogram, a league crest, and a broadcast lockup with a LIVE tag and a tagline bar, and judged them at 200 pixels on light and dark backgrounds. The broadcast lockup won. The crest became the app icon.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fthunderbashers\u002Fv11-menu.webp\" alt=\"The v1.1 title screen: broadcast lockup over a floodlit storm pit\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"744\">\n\u003C\u002Ffigure>\n\u003Cp>The first UI kit was clean and forgettable: rounded rectangles, one accent colour. The second pass borrowed the logo’s language, chamfered corners, diagonal speed lines, hazard stripes, LIVE red for danger states. Same nine-slice borders and pixel sizes, different personality. Every piece sits behind one flag, and turning the flag off gives you the jam build back.\u003C\u002Fp>\n\u003Ch2>Stage 2: new models, same rig\u003C\u002Fh2>\n\u003Cp>The jam robot was seven sprites: body, head, two arms, three wheel views, assembled by an editor script with fixed pivots. I did not want to rewrite that. So the new robot was built as a low-poly model and rendered from the game’s own camera angle into the same seven parts at twice the pixel size, with a validation script checking every pivot against what the engine generated. The prefabs went through the existing builder, and the assembled robot’s bounds landed within three percent of the original.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fthunderbashers\u002Foverhaul-robot-rig-review.webp\" alt=\"Rig review: the shipped robot beside the rendered model, with team tint and a second face\" loading=\"lazy\" decoding=\"async\" width=\"1440\" height=\"1000\">\n\u003C\u002Ffigure>\n\u003Cp>Honest verdict on that stage: technically perfect, artistically a step sideways. The hand-drawn jam robot, with its bulb antenna and one eye, had more personality than the clean model. The pipeline was right and the brief was too loose. I kept the pipeline and sent the character back for a personality pass, which gave it the slightly foolish expression it has now.\u003C\u002Fp>\n\u003Ch2>Stage 3: the frame is one picture\u003C\u002Fh2>\n\u003Cp>Play-testing the stage 2 build produced the note that mattered most: the aim arrow was awkward, the spin ring felt like a shrinking outline rather than a charge, and the backgrounds were horrible. The floor was a grid, past the rim was nothing, the sky was stars. No amount of robot work fixes a scene with no ground.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fthunderbashers\u002Foverhaul-frame-pit.webp\" alt=\"A live match on The Pit after stage 3: worn floor, riveted LED wall ring, painted centre decal\" loading=\"lazy\" decoding=\"async\" width=\"1347\" height=\"759\">\n\u003C\u002Ffigure>\n\u003Cp>Stage 3 treated the whole frame as one image: a floor per arena, a wall ring with depth, stadium seating and floodlight rigs behind the walls, storm skies, a vignette I can dial with one number, a chunky aim chevron, and a twelve-frame spin ring that pulls inward on the existing windup timing. Loading screens with league tips, round cards, and danger alerts landed in the same stage because they share the language.\u003C\u002Fp>\n\u003Ch2>The style seam\u003C\u002Fh2>\n\u003Cp>Looking at that frame I could see the next problem. The floor and skies were painterly. The robots were flat-shaded with ink outlines. Each was fine alone. Together they read as two games. So stage 4 started with a written style bible and one rule: everything in the match frame is rendered the same way. Flat three-tone shading, one ink weight, one lighting rig, and the stadium rebuilt as low-poly geometry in the robots’ style. Painterly work moved to loading screens and marketing, where it belongs.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fthunderbashers\u002Fv11-match.webp\" alt=\"Stage 4: the whole match frame in one flat-shaded style, with the rebuilt ability slots\" loading=\"lazy\" decoding=\"async\" width=\"1347\" height=\"626\">\n\u003C\u002Ffigure>\n\u003Cp>Closing the seam opened the next note: unified and empty. So the dressing pass put wear, silhouettes behind the rails, and light falloff back inside the flat language, and gave each of the seven arenas its own venue.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fthunderbashers\u002Farena-variants.webp\" alt=\"Blender layout review of the seven arena variants before they went into Unity\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"750\">\n\u003C\u002Ffigure>\n\u003Ch2>The part you can hear and feel\u003C\u002Fh2>\n\u003Cp>Once the frame held still, the last stages were about the hit. A strike now has hit-stop on a curve, an impact flash, punch-zoom, shake, a vignette pulse, and a pitch dip in the audio, all driven by one feel profile I can tune in the inspector. The metallic ball hits came back from the jam’s sound library as a base layer, with procedural transients on top so no two hits sound the same. Forty-four procedural sound effects, a mixer with music, SFX, and UI buses tied to the settings screen, gamepad bindings with glyph hints, and an announcer who calls countdowns, streaks, and the champion finished it.\u003C\u002Fp>\n\u003Ch2>What I’d keep\u003C\u002Fh2>\n\u003Cp>Stage by visibility per hour, and put animation last. Guard every stage with a flag so the shipped game never stops being shippable. Judge in the engine with the real camera and light, because renders and mock-ups lie about scale and glow. And paste the previous play-test note verbatim at the top of the next brief, so the work answers the actual complaint and not a paraphrase of it.\u003C\u002Fp>\n\u003Cp>Version 1.1 went live on Wavedash on September 7. The online update followed it: a Garage for naming and decorating your robot, and rooms with host-authoritative netcode over the Wavedash SDK. That one was built on the same rule, the offline simulation untouched underneath, with the network layer sitting beside it instead of inside it.\u003C\u002Fp>\n",{"slug":343,"title":344,"dek":345,"date":346,"project":276,"tags":347,"variants":350,"order":99,"readingMinutes":134,"contentHtml":351},"spare-knight-jam-to-steam","Spare Knight: choosing the engine you can ship","Why I parked a from-scratch Unity rebuild, kept the jam codebase, and pointed it at Steam.","2026-09-06",[265,281,279,348,291,349],"phaser","steam",[5,151],"\u003Cp>Five months after the jam, \u003Cem>Spare Knight\u003C\u002Fem> had two codebases. The Phaser build that shipped in April was live on \u003Ca href=\"http:\u002F\u002Fitch.io\">itch.io\u003C\u002Fa> with a tutorial run, fog of war, a node map, and my painted cast. The second was a Unity 6 rebuild I had started in July: an HD-2D look with 3D tile meshes under painted billboards, a pure C# rules engine with about a thousand tests, and a cleaner design document. It was better engineered in every way I could measure. On September 5 I parked it.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fprocess\u002Fspare-knight-timeline.webp\" alt=\"Timeline of the Spare Knight engine decision\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"640\">\n\u003C\u002Ffigure>\n\u003Ch2>The test that decided it\u003C\u002Fh2>\n\u003Cp>The rebuild’s promise was light. Real shadows, a camera that could pitch and orbit, elevation, weather. To check whether the characters could live in that world I put a low-poly 3D Cog in the sandbox scene next to the painted one, same height, same lighting. The 3D knight won on turning and on how it sat in the light. The painted one won on everything a player looks at: the brush texture, the tilt of the helmet, the expression in one glowing eye. It was not close. Props and tiles were fine in 3D. Characters were not, and characters are the game.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fspare-knight\u002Fcog-3d-look-tests.webp\" alt=\"Look test: the 3D Cog beside the painted one under the same light and camera\" loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"830\">\n\u003C\u002Ffigure>\n\u003Cp>That result reframed the whole project. The friction in the rebuild was never the engine. It was art direction, and it was permanent.\u003C\u002Fp>\n\u003Ch2>What “ship” meant\u003C\u002Fh2>\n\u003Cp>The goal had changed since April. \u003Cem>Spare Knight\u003C\u002Fem> was no longer a jam entry to polish. It was going to be my first sold desktop release, on Steam, with itch as the beta channel. I work on it around ten hours a week. Every hour on the rebuild had gone into infrastructure. Every hour on the Phaser build went into the game.\u003C\u002Fp>\n\u003Cp>I wrote the decision down as an architecture record so I would stop relitigating it. The Phaser codebase ships, wrapped in Electron with Steamworks. The Unity project stays on disk as an idea bank, and when a rule from it is worth having, I port the behaviour by writing the JavaScript test from the C# test’s observable rule, never the code. The Phaser Editor tooling was retired the same day. Code-first is faster for one person.\u003C\u002Fp>\n\u003Ch2>Making the drawing the bottleneck\u003C\u002Fh2>\n\u003Cp>If the painted characters are the reason the game exists, the pipeline should make painting the only slow step. Before, a new animation meant exporting frames from Krita, renaming them by hand, and editing a manifest. Now the export folder is the contract: drop frames into a folder named for the entity and the animation, run one command, and the entity appears in the game with default timing. A CI check fails the build if the generated manifest is stale. From a Krita export to a moving sprite takes under a minute.\u003C\u002Fp>\n\u003Cp>The same week I picked the direction for what comes next: a bigger cast of stranger machines, drawn with the boldness of Edmund McMillen’s silhouettes. Draw more, tool less.\u003C\u002Fp>\n\u003Ch2>Redesigning the hero without losing him\u003C\u002Fh2>\n\u003Cp>Cog’s diving-helmet look had carried the jam and I was tired of it. Rather than iterate on the existing drawing, I ran the redesign as a silhouette pass: six directions, each with a name and a one-line idea, judged at 64 pixels against written criteria. Does it say “knight” in one glance? Does it read old and loyal? Is it distinct from every generic diving-helmet robot?\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fspare-knight\u002Fcog-silhouettes.webp\" alt=\"Six silhouette directions for Cog, judged at 64 pixels\" loading=\"lazy\" decoding=\"async\" width=\"1536\" height=\"1024\">\n\u003C\u002Ffigure>\n\u003Cp>Those boards are exploration, the way a mood board is exploration. The finished character will be painted, like every character in the shipped game, because the look test above already settled that nothing else sits beside Wren.\u003C\u002Fp>\n\u003Cp>The winner was “The Oathwall”: a great helm with a lit visor slit, one enormous moss-eaten shield worn as a pauldron, a torn red tabard with a faded device, long open hands, and a stoop. The inspiration list was deliberate. The robot soldier from \u003Cem>Castle in the Sky\u003C\u002Fem> for an ancient guardian who moves gently. \u003Cem>The Iron Giant\u003C\u002Fem> for mass and a weapon that chose otherwise. A 13th-century great helm so the reader says “knight” without being told. Next to Wren, the human he protects, the design tells the whole story in one frame.\u003C\u002Fp>\n\u003Ch2>Where it stands\u003C\u002Fh2>\n\u003Cp>Since the decision the desktop build has grown into a beta: runs resume from map checkpoints, saves recover from a corrupted file, and the interface moved to a black-and-paper language with near-black silhouettes and bone paper controls. None of that is published yet. The build on \u003Ca href=\"http:\u002F\u002Fitch.io\">itch.io\u003C\u002Fa> is still the July UI overhaul, and it stays that way until the beta survives a clean-machine install.\u003C\u002Fp>\n\u003Ch2>What I’d keep\u003C\u002Fh2>\n\u003Cp>Decide with a look test, not a spreadsheet. The 3D-versus-painted comparison took an evening and ended a two-month argument I had been having with myself. Write the decision down with its reasons so it stays decided. And protect the one slow, human step in the pipeline by making everything around it fast.\u003C\u002Fp>\n",{"slug":353,"title":354,"dek":355,"date":327,"project":263,"tags":356,"variants":358,"order":99,"readingMinutes":134,"contentHtml":359},"i-wont-be-abducted-postmortem","I Won't Be Abducted: a 63-hour postmortem","A weekend tabletop-defense game, the economy I cut, and the one ending everything else exists to protect.",[265,266,292,357],"narrative-design",[5,151],"\u003Cp>I built \u003Cem>I Won’t Be Abducted\u003C\u002Fem> in about 63 hours for Wavedash Spring Jam 26. The theme was \u003Cstrong>Shelter\u003C\u002Fstrong>. I did the design, the code, and the direction, and drew the characters and props by hand. It was my first Godot game (\u003Cem>Spare Knight\u003C\u002Fem> was Phaser), and it went live on \u003Ca href=\"http:\u002F\u002Fitch.io\">itch.io\u003C\u002Fa> and Wavedash the same night.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Spoiler warning: the ending is discussed below, and the ending is the design.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>\u003Cem>September update: this piece is about the original jam build. \u003Ca href=\"\u002Fnotes\u002Fi-wont-be-abducted-after-the-jam?variant=game-design\">The follow-up covers the expanded game and what players could not read\u003C\u002Fa>.\u003C\u002Fem>\u003C\u002Fp>\n\u003Ch2>The pitch\u003C\u002Fh2>\n\u003Cp>A boy refuses to leave his room and defends it from a nightly alien invasion. Except the aliens are board-game pieces. The giant hands placing them are his own. The final boss is his Dad opening the bedroom door, and beating the monster and the father getting through are the same event. The camera pulls back, and it was always his room, his hands, his game.\u003C\u002Fp>\n\u003Cp>The reference in my head was the \u003Cem>Fort\u002FDa\u003C\u002Fem> ritual: a child mastering something he cannot control by turning it into a game he can. The jam asked for shelter. The room was never the shelter. The play was.\u003C\u002Fp>\n\u003Ch2>Write the “do not build” list first\u003C\u002Fh2>\n\u003Cp>Before any code I wrote a three-column scope table: must ship, stretch, and \u003Cstrong>do not build\u003C\u002Fstrong>. The third column did the most work. Procedural maps, a real combo system, free-roam pathfinding, and a fourth night were banned on day zero no matter how much time was left. The rule underneath: the night battle is the game, and everything else is a setting on it. The board is 3x5, fifteen cells, and it never apologizes for being small.\u003C\u002Fp>\n\u003Cp>The biggest mid-jam cut was the economy. The original design had a shop between nights where Scrap bought upgrades. That meant two kinds of tuning debt at once, pricing every upgrade and pacing every income source, with no time to playtest either. The shop became a free dawn draft, pick one of three cards from a pool of five, and Scrap kept exactly one sink: crafting kickable obstacles mid-fight. A draft balances itself in a way a shop does not, because the player only ever compares three options I chose to put in front of them.\u003C\u002Fp>\n\u003Ch2>What worked\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>Data over code.\u003C\u002Fstrong> Every gameplay number lives in a \u003Ccode>.tres\u003C\u002Fcode> resource, so the last night of the jam was spent turning dials instead of editing scripts. I wrote about that separately in \u003Ca href=\"\u002Fnotes\u002Ftuning-fear-by-numbers\">Tuning fear by numbers\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Signals over references.\u003C\u002Fstrong> Gameplay emits events on a global bus; the UI listens. The HUD, the audio, the achievements, and the panic shader all subscribe to signals the game logic was already firing. Nothing in gameplay knows the UI exists.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>The tabletop conceit paid for itself.\u003C\u002Fstrong> Board-game standees do not need walk cycles. All the motion is code: hops, lunges, knock-backs, screen shake, a shader that drains colour as your Nerve drops. That one constraint moved weeks of animation off the schedule, and it made the twist land harder because the pieces always looked like pieces.\u003C\u002Fp>\n\u003Ch2>What didn’t\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>The roster came in too late.\u003C\u002Fstrong> The Crawler was playable from the first session. The Lobber, the Rusher, and the Dad fight stayed data-only until the final day. They shipped, but they got hours of playtest instead of days, and early balance showed it.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>Fairness arrived last.\u003C\u002Fstrong> Enemy attacks had no wind-up until the finish pass. Until the telegraphs went in, pull back, warning flash, then strike, hits felt arbitrary. A game where you dodge by stepping off a tile is only fair when every hit announces itself. That should have been built with the first enemy, not the last.\u003C\u002Fp>\n\u003Cp>\u003Cstrong>The intro plan was four times too big.\u003C\u002Fstrong> The script called for four voiced narration parts. I recorded one. The cinematic shipped with beat one voiced and captioned and the rest as timed text over the music. That was the right cut, made a day later than it should have been.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fi-wont-be-abducted\u002Froom-plain.webp\" alt=\"The bedroom board at night\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"872\">\n\u003C\u002Ffigure>\n\u003Ch2>What I’d keep\u003C\u002Fh2>\n\u003Cp>The discipline of the reveal. Every system, the hands, the standees, the game-over art kept deliberately vague, exists to protect one quiet beat at the end. Shipping a complete arc in a weekend came down to knowing which single moment the whole game was for and cutting toward it.\u003C\u002Fp>\n",{"slug":361,"title":362,"dek":363,"date":327,"project":276,"tags":364,"variants":367,"order":116,"readingMinutes":116,"contentHtml":368},"spare-knight-postmortem","Spare Knight: 13 days, no dice","Why attacks never miss, where the randomness went instead, and what painting a whole cast taught me about scope.",[265,348,292,365,366],"combat-design","art",[5,151],"\u003Cp>\u003Cem>Spare Knight\u003C\u002Fem> was my first shipped game: a tactical roguelike built in 13 days for Gamedev.js Jam 2026, theme \u003Cstrong>Machines\u003C\u002Fstrong>. I directed it, wrote the code in Phaser 3 with Vite, and painted the characters, the event illustrations, and the interface in Krita on a Wacom tablet. Nicholas Drabb joined for sound, voice, and the lore we wrote together. It shipped open source, on the jam page and on \u003Ca href=\"http:\u002F\u002Fitch.io\">itch.io\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>The premise carries the theme. The machines have reached a philosophical consensus that organic life is inefficient. They call it the Absolution Mindset. You are a knight whose firmware is three generations out of date, too old to receive the update, with one human left to protect.\u003C\u002Fp>\n\u003Ch2>The core bet: no attack RNG\u003C\u002Fh2>\n\u003Cp>The first design decision was the one everything else hangs on: attacks never miss. No hit chance, no damage range. Combat happens on a 6x6 isometric grid with speed-based initiative, and every choice is settled the moment you make it.\u003C\u002Fp>\n\u003Cp>The reasoning is about run length. A run is 15 to 20 minutes. In a run that short, a 75 percent roll that misses twice in a row does not read as variance. It reads as theft. XCOM can afford that feeling across a 30-hour campaign. A jam roguelike cannot. So the dice came out, and the tension had to come from somewhere else.\u003C\u002Fp>\n\u003Cp>It moved into execution. Offense and defense both run through timing reactions: a strike wheel and a brace wheel where you hear a chime and commit on the beat. The math of an exchange is fully deterministic. Whether you flubbed the timing is on you. Randomness still exists in the game, but it lives in the run layer, a node map across two acts, cornfield to cathedral to silo, then the Herald, then the Prelate, where variance creates replayability instead of resentment.\u003C\u002Fp>\n\u003Cfigure class=\"prose-media\">\n  \u003Cimg src=\"\u002Fshots\u002Fsystems\u002Fspare-knight-systems.svg\" alt=\"Systems map: initiative, committed choices, the reaction wheel, and where randomness lives\" loading=\"lazy\" decoding=\"async\" width=\"1600\" height=\"900\">\n\u003C\u002Ffigure>\n\u003Cp>The companion decision: defeat is a moment, not a punishment. Short runs, instant restart, and a tone that treats falling as part of the fiction of being obsolete. People retry an unfair game out of spite and a fair one out of curiosity. Only one of those survives a jam rating page.\u003C\u002Fp>\n\u003Ch2>Painting the cast\u003C\u002Fh2>\n\u003Cp>The look I wanted was what I called Bright Ruins: warm, sunlit, weighty, somewhere between \u003Cem>Don’t Starve\u003C\u002Fem>’s linework and a Ghibli afternoon. I painted Cog, Wren, every machine, the reaction wheels, the icons, the logo, and the nine event illustrations. The characters are the part I am proud of and the part people respond to.\u003C\u002Fp>\n\u003Cp>Painting a cast alone on a deadline taught me the real unit cost of art. Two things kept it survivable. The battle happens on one screen, so the tile vocabulary stayed small. And a consistent brush kit in Krita meant each new character inherited the style for free instead of needing a style decision.\u003C\u002Fp>\n\u003Cp>The unexpected benefit of directing art and code from the same chair: when a sprite read badly in the engine, the painter already knew which layer to fix.\u003C\u002Fp>\n\u003Ch2>Working with a collaborator while building\u003C\u002Fh2>\n\u003Cp>Nicholas owned sound, voice, and half the narrative, and consulted on design. What worked was one design document as the source of truth, with his deliverables plugging into hooks that already existed, audio events and dialogue beats. What I would change: I gave him lore in bursts instead of up front, and it cost us a rewrite of the late-game text.\u003C\u002Fp>\n\u003Ch2>Lessons that carried forward\u003C\u002Fh2>\n\u003Cp>Lock the mechanical core in week one. The timing wheels were the riskiest element, and they were prototyped and felt right before any content was built around them. If they had failed, the game would have become something else early instead of late. Scope the art to the camera, not to ambition. And ship to more than one storefront, because the jam page and itch brought different players and different feedback.\u003C\u002Fp>\n\u003Cp>Two months later I applied all three in a 63-hour Godot jam. That one has \u003Ca href=\"\u002Fnotes\u002Fi-wont-be-abducted-postmortem\">its own postmortem\u003C\u002Fa>.\u003C\u002Fp>\n","2026-09-08T06:47:57.543Z",{"variant":371,"profile":390,"experience":397,"projects":398,"skills":571,"writings":584,"generatedAt":625},{"key":151,"label":372,"headline":373,"tagline":374,"heroHeading":9,"heroProject":289,"heroAccent":11,"heroSub":375,"accent":376,"sectionOrder":377,"featuredTags":378,"projectGroups":380,"projectsHeading":385,"resumeUrl":386,"ctaPrimary":387,"availability":388,"aboutHtml":389},"Games","Gameplay Engineer","Gameplay engineer and designer in Orlando. Four shipped games in Unity, Phaser, and Godot.","I'm a software engineer in Orlando who designs and builds games. Here you'll find what I've shipped, the systems underneath, and what I learned making them.","accent-2",[15,16,19,18,20],[379],"game-dev",[381,383],{"type":382,"label":372},"game",{"type":24,"label":384},"Tools & AI","Selected games","\u002Fresume\u002FChristian-DAlbano-Game-Design-Resume.pdf","Explore games","Open to gameplay engineering and game design roles, Orlando or remote","\u003Cp>I make games alongside my software engineering work. Four have shipped: a tactical roguelike in Phaser, a tabletop defense game in Godot, and two Unity games, an arena brawler and a card roguelike, all playable in a browser. I do the design and the programming, and I draw a good part of the art myself.\u003C\u002Fp>\n\u003Cp>What I care about is how rules interact and how a player learns them. In Undead Signal that meant writing the rules for five days before building, then balancing with a bot that played 300 seeded runs per configuration. In ThunderBashers it meant finding that a mechanic that felt broken was a readability problem, and fixing it with a telegraph instead of a rules change. In I Won’t Be Abducted it meant moving the boss’s instructions onto the board above his head. Each project page has a systems map of how the game actually works.\u003C\u002Fp>\n\u003Cp>I’m looking for gameplay engineering or game design work where I can keep doing that: build the system, watch people play it, and fix the right thing. If you want the short version, \u003Ca href=\"\u002Fresume\u002FChristian-DAlbano-Game-Design-Portfolio.pdf\">the portfolio deck\u003C\u002Fa> is fourteen printable pages: one rule, one systems map, and one measured result per game.\u003C\u002Fp>\n",{"name":32,"headline":7,"location":33,"email":34,"site":35,"socials":391,"bioHtml":57},[392,393,394,395,396],{"label":38,"url":39,"icon":40},{"label":42,"url":43,"icon":44},{"label":46,"url":47,"icon":48},{"label":50,"url":51,"icon":52},{"label":54,"url":55,"icon":56},[],[399,403,441,445,449,494,531],{"slug":10,"name":141,"blurb":142,"contribution":143,"url":144,"repo":11,"type":24,"tags":400,"variants":401,"featured":152,"order":74,"cover":153,"wavedash":11,"engine":11,"jam":11,"media":402,"descriptionHtml":155},[146,147,96,148,86,66,149],[5,151],[],{"slug":289,"name":404,"blurb":405,"contribution":406,"url":407,"repo":11,"type":382,"tags":408,"variants":413,"featured":152,"order":74,"cover":414,"wavedash":407,"engine":415,"jam":416,"media":417,"descriptionHtml":440},"Undead Signal","A zombie card roguelike where the horde forms poker hands and every move you have takes their hand apart.","Rules, systems and balance design, direction, and the hand-drawn zombie portraits.","https:\u002F\u002Fwavedash.com\u002Fgames\u002Fundead-signal",[379,291,409,410,294,411,329,293,412,339],"csharp","webgl","roguelike","wavedash-sdk",[151],"\u002Fshots\u002Fundead-signal.webp","Unity 6","Wavedash game jam",[418,422,425,428,431,434,437],{"src":419,"alt":420,"kind":421},"\u002Fshots\u002Fundead-signal\u002Fcover.webp","Undead Signal cover: distressed bone lettering beside ink-drawn zombies, a field radio and a broadcast tower","image",{"src":423,"alt":424,"kind":421},"\u002Fshots\u002Fundead-signal\u002Ftable-board.webp","The Survivor's Table (September update): the horde's hand reads 87, two pair times three, on a paper note beside five zombie cards, with the Discover pile, your pack, and the found and equipped trays around the table",{"src":426,"alt":427,"kind":421},"\u002Fshots\u002Fsystems\u002Fundead-signal-systems.svg","Systems map: the THREAT equation, the two levers that lower it, the price of every verb, the run loop, and the balance-lab numbers",{"src":429,"alt":430,"kind":421},"\u002Fshots\u002Fundead-signal\u002Ftable-aim.webp","Dragging a crowbar onto a rank-5 Bloated: the forecast note says kill, bite-back 0, threat 16 to 11, one action, three uses left",{"src":432,"alt":433,"kind":421},"\u002Fshots\u002Fundead-signal\u002Ftable-reader.webp","Right-click inspection: a full-size hand-drawn Skinny portrait beside paper field notes explaining rank, the Runner rule, and the scoring hand",{"src":435,"alt":436,"kind":421},"\u002Fshots\u002Fundead-signal\u002Ffield-guide.webp","The field guide's keyword tab: 31 rule terms and ten hand patterns, each readable on hover",{"src":438,"alt":439,"kind":421},"\u002Fshots\u002Fundead-signal\u002Fhorde-chase.webp","The July build: a five-zombie hand on the table with the threat equation reading sum 19 times 2 equals 38","\u003Cp>You are a radio engineer restoring the signal in locations the dead have overrun. The horde deals itself poker hands. Whatever survives to the exit forms its best five-card hand, and that hand’s threat hits your health. You have two ways to lower it: drop the sum by killing or shrinking high ranks, or break the structure by scattering a pair or splitting a straight. Breaking their hand is fixing the bad connection.\u003C\u002Fp>\n\u003Cp>Every verb costs something. Digging for loot feeds the horde. Loud weapons draw more cards. Weapons wear out, and a hit that fails to kill leaves you open to a bite. Rank doubles as health, so shrinking a zombie can accidentally complete three of a kind and raise the threat you were trying to lower. That interaction was never designed. It fell out of two rules colliding, and it is the one players quote back to me.\u003C\u002Fp>\n\u003Cp>I spent the first five days of the jam on rules alone, killed three drafts, and locked a design document before the build. Balance came from a bot playing 300 seeded runs per configuration: the win rate went from a measured 19 percent to 37 percent across two tuning passes, and those reports still re-run. \u003Ca href=\"\u002Fnotes\u002Fundead-signal-postmortem\">The postmortem\u003C\u002Fa> has the numbers.\u003C\u002Fp>\n\u003Cp>The September update, the Survivor’s Table, rebuilt the interface around one idea: every question the player has should be answered where they are already looking. Drag a weapon onto a card and the forecast appears beside the target. Right-click any card for a full-art dossier. Hover a keyword and its rule appears. Thirty-one terms and ten hand patterns live in a field guide. The game runs on Wavedash with ten achievements, two leaderboards, and cloud saves.\u003C\u002Fp>\n",{"slug":157,"name":158,"blurb":159,"contribution":160,"url":161,"repo":11,"type":24,"tags":442,"variants":443,"featured":152,"order":99,"cover":168,"wavedash":11,"engine":11,"jam":11,"media":444,"descriptionHtml":170},[163,164,86,165,66,166,89],[5,151],[],{"slug":172,"name":173,"blurb":174,"contribution":175,"url":176,"repo":11,"type":24,"tags":446,"variants":447,"featured":152,"order":99,"cover":181,"wavedash":11,"engine":11,"jam":11,"media":448,"descriptionHtml":183},[146,147,97,164,178,86,179],[5,151],[],{"slug":303,"name":450,"blurb":451,"contribution":452,"url":453,"repo":11,"type":382,"tags":454,"variants":458,"featured":152,"order":99,"cover":459,"wavedash":460,"engine":415,"jam":461,"media":462,"descriptionHtml":493},"ThunderBashers","An arena brawler where spinning is your only verb: strike the storm ball, parry it, or get knocked out of the league. Solo campaign and online rooms.","Design, direction, programming, bot AI and feel tuning, and the jam's hand-drawn art.","https:\u002F\u002Fchrisdbgames.itch.io\u002Fthunderbashers",[379,291,409,410,455,306,305,456,457,292],"arena-brawler","netcode","multiplayer",[151],"\u002Fshots\u002Fthunderbashers.webp","https:\u002F\u002Fwavedash.com\u002Fgames\u002Fthunder-bashers","Very Serious Juniper Dev Game Jam",[463,466,469,472,475,478,481,484,487,490],{"src":464,"alt":465,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Fcover.webp","ThunderBashers key art: the broadcast-style league lockup over a floodlit storm pit",{"src":467,"alt":468,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Fv11-menu.webp","v1.1 title screen: the league logo, a storm sky, and floodlights on the empty pit behind the menu",{"src":470,"alt":471,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Fv11-match.webp","v1.1 match: three flat-shaded robots with nameplates on the pit floor, the ball between them, Spin and Dash slots in the corner",{"src":473,"alt":474,"kind":421},"\u002Fshots\u002Fsystems\u002Fthunderbashers-systems.svg","Systems map: the spin state machine, the ball's heat-gated lethality, the bot priority order, and the BotProfile data fields",{"src":476,"alt":477,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Ffinal-champion.webp","Champion screen: a spotlit podium and a league certification card",{"src":479,"alt":480,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Farena-variants.webp","Blender layout review of seven arena variants for the dressing pass: The Pit, The Gauntlet, The Diamond, HexCell, GridLock, VoidPit, and the Boss Arena",{"src":482,"alt":483,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Fgarage.webp","The Garage: an operator names their robot, picks colours, and places stickers on a live rig preview",{"src":485,"alt":486,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Fonline-room.webp","An online room: two ready operators, chat, room settings, and the host's start button",{"src":488,"alt":489,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Farena-diamond.webp","The original jam build: hand-drawn robots circle the thunder-ball in The Diamond arena",{"src":491,"alt":492,"kind":421},"\u002Fshots\u002Fthunderbashers\u002Fheat-ball.webp","The jam build's thunder-ball glowing red-hot, the telegraph that says it is lethal","\u003Cp>Robots in a circular pit bash a storm-charged ball at each other. Spinning is the whole kit: it strikes the ball, parries an incoming hit, and shoves a rival in close. If the ball catches you while you are not spinning, you are out. Last one standing wins. Five bot personalities, each a data profile for reaction time, aim, and nerve, fill a three-arena campaign.\u003C\u002Fp>\n\u003Cp>I built the jam version in five days after switching from Unreal to Unity mid-jam, and drew the robots and arena by hand. The mechanic that took longest was the one that sounded easiest: the ball kills you. It only started to feel fair once the ball showed its own danger with a heat tint. \u003Ca href=\"\u002Fnotes\u002Fthunderbashers-postmortem\">That story is in the postmortem\u003C\u002Fa>.\u003C\u002Fp>\n\u003Cp>Version 1.1, live on Wavedash since September, is a staged visual overhaul: a league identity and UI kit, robots rebuilt as models and rendered back into the original sprite rig, one flat-shaded style for the whole match frame, seven dressed arenas, announcer voice lines, procedural sound, and a strike-feel pass with hit-stop and punch-zoom. Every stage sits behind a flag, so the shipped game stayed playable at every commit. \u003Ca href=\"\u002Fnotes\u002Fthunderbashers-visual-overhaul\">The overhaul write-up\u003C\u002Fa> covers the order and the reasoning.\u003C\u002Fp>\n\u003Cp>Online play followed. You name and decorate your robot in the Garage, then open a room on Wavedash, public or locked with a password, and fight up to three other operators with bots filling any empty seats. The netcode is host-authoritative on top of the untouched single-player simulation: clients send input, the host runs the same robot and ball code that ships offline, and state comes back over the Wavedash SDK’s lobbies and peer-to-peer transport. Slow-motion on a strike stays local to the striker’s screen, because a global time scale cannot be shared.\u003C\u002Fp>\n",{"slug":276,"name":495,"blurb":496,"contribution":497,"url":498,"repo":499,"type":382,"tags":500,"variants":505,"featured":152,"order":116,"cover":506,"wavedash":11,"engine":243,"jam":507,"media":508,"descriptionHtml":530},"Spare Knight","A tactical roguelike where attacks never miss and the tension lives in your timing, with characters I painted by hand.","Direction, programming, and the painted characters, event illustrations, and interface art; a collaborator handled sound, voice, and co-wrote the lore.","https:\u002F\u002Fchrisdbgames.itch.io\u002Fspare-knight","https:\u002F\u002Fgithub.com\u002FChrisdalbano\u002FSpareKnight-webgame",[379,348,88,411,501,502,503,504,292],"tactical","isometric","ui-systems","hand-painted",[151],"\u002Fshots\u002Fspare-knight.webp","Gamedev.js Jam 2026",[509,512,515,518,521,524,527],{"src":510,"alt":511,"kind":421},"\u002Fshots\u002Fspare-knight\u002Fcover.webp","Spare Knight key art: the brush-lettered title with its gear over the Act 1 countryside",{"src":513,"alt":514,"kind":421},"\u002Fshots\u002Fspare-knight\u002Fcombat-act1.webp","Act 1 combat: Cog and the machines on the isometric grid in the cornfield arena",{"src":516,"alt":517,"kind":421},"\u002Fshots\u002Fsystems\u002Fspare-knight-systems.svg","Systems map: speed initiative, committed choices with no attack RNG, the reaction wheel, where randomness lives, and the interface token system",{"src":519,"alt":520,"kind":421},"\u002Fshots\u002Fspare-knight\u002Fnodemap-act1.webp","Act 1 node map: branching run paths across the Bright Ruins countryside",{"src":522,"alt":523,"kind":421},"\u002Fshots\u002Fspare-knight\u002Fcombat-act2.webp","Act 2 combat: the cathedral approach",{"src":525,"alt":526,"kind":421},"\u002Fshots\u002Fspare-knight\u002Fcamp.webp","The camp scene between battles, warm and sunlit",{"src":528,"alt":529,"kind":421},"\u002Fshots\u002Fspare-knight\u002Fevent-wren.webp","Event illustration: Wren whispers to the knight","\u003Cp>An outdated knight protects the last human he has in a world where the machines have decided organic life is inefficient. Combat runs on a 6x6 isometric grid with speed setting the turn order. Attacks never miss and never roll damage; what you commit is what happens. The tension moved into timing instead: reaction wheels ask you to hear a chime and choose when to strike or brace.\u003C\u002Fp>\n\u003Cp>I directed the game, wrote the code in Phaser, and painted Cog, Wren, the machines, the event illustrations, and the interface in Krita. Nicholas Drabb handled sound, voice, and co-wrote the lore. It shipped in 13 days for Gamedev.js Jam 2026 and is open source.\u003C\u002Fp>\n\u003Cp>Players told me the original interface was hard to read, so the July update rebuilt it the way I would build a web app: named type roles, panel tokens, and one shared tooltip component that every scene uses. That build is the one live on \u003Ca href=\"http:\u002F\u002Fitch.io\">itch.io\u003C\u002Fa> today. \u003Ca href=\"\u002Fnotes\u002Fspare-knight-postmortem\">The postmortem\u003C\u002Fa> covers the combat design and \u003Ca href=\"\u002Fnotes\u002Fspare-knight-ui-overhaul\">the UI devlog\u003C\u002Fa> covers the rebuild.\u003C\u002Fp>\n\u003Cp>Since then I decided the game’s future: the Phaser codebase, wrapped for desktop, aimed at Steam, with a parked Unity rebuild kept as an idea bank. A desktop beta with resumable runs and a black-and-paper interface exists locally and has not been published yet. \u003Ca href=\"\u002Fnotes\u002Fspare-knight-jam-to-steam\">Choosing the engine you can ship\u003C\u002Fa> explains the call.\u003C\u002Fp>\n",{"slug":263,"name":532,"blurb":533,"contribution":534,"url":535,"repo":11,"type":382,"tags":536,"variants":540,"featured":152,"order":134,"cover":541,"wavedash":542,"engine":543,"jam":544,"media":545,"descriptionHtml":570},"I Won't Be Abducted","A boy defends his bedroom from a nightly alien invasion in a tabletop game where the aliens are pieces and the hands are his.","Design, programming, direction, and the hand-drawn standees.","https:\u002F\u002Fchrisdbgames.itch.io\u002Fi-wont-be-abducted",[379,266,537,538,331,539,267,292],"gdscript","tabletop-defense","narrative",[151],"\u002Fshots\u002Fi-wont-be-abducted.webp","https:\u002F\u002Fwavedash.com\u002Fgames\u002Fi-won-t-be-abducted","Godot 4.6","Wavedash Spring Jam 26",[546,549,552,555,558,561,564,567],{"src":547,"alt":548,"kind":421},"\u002Fshots\u002Fi-wont-be-abducted\u002Fcover.webp","Key art: the boy stands his ground in the bedroom doorway",{"src":550,"alt":551,"kind":421},"\u002Fshots\u002Fi-wont-be-abducted\u002Fplacement-2026-09.webp","Night four, fight paused: legal cells glow green while a tin can is placed on the board beside the boy and two aliens",{"src":553,"alt":554,"kind":421},"\u002Fshots\u002Fsystems\u002Fi-wont-be-abducted-systems.svg","Systems map: the fixed PlayerConfig, the per-night NightConfig dials, the enemy pool, the Scrap and workbench loop, and the EventBus fan-out to HUD, audio and shader",{"src":556,"alt":557,"kind":421},"\u002Fshots\u002Fi-wont-be-abducted\u002Fopening-2026-09.webp","The boss's KICK NOW cue with its countdown and highlighted adjacent cells during an opening",{"src":559,"alt":560,"kind":421},"\u002Fshots\u002Fi-wont-be-abducted\u002Fworkbench-2026-09.webp","The workbench: three illustrated recipes with coin costs, and a wallet forecast above the pockets",{"src":562,"alt":563,"kind":421},"\u002Fshots\u002Fi-wont-be-abducted\u002Fboss-entrance-2026-09.webp","The final night's entrance: a giant hand lowers the boss onto the board while the boy waits",{"src":565,"alt":566,"kind":421},"\u002Fshots\u002Fi-wont-be-abducted\u002Fdawn-2026-09.webp","Dawn between nights: one free upgrade from three, and supplies for sale with the Scrap balance shown",{"src":568,"alt":569,"kind":421},"\u002Fshots\u002Fi-wont-be-abducted\u002Fpreview-chars.webp","Hand-drawn papercraft standees: the boy and the alien pieces","\u003Cp>A boy refuses to leave his room and defends it from an alien invasion across four nights. You charge and kick pieces off the board through the door and windows, build tools at a workbench, and pick one upgrade at dawn. The ending pulls the camera back to show who has been moving the pieces.\u003C\u002Fp>\n\u003Cp>The bet I made before writing any code was that the player’s kit never changes. Every number that makes a night harder lives in a Godot resource, so the curve is a set of dials rather than new mechanics. \u003Ca href=\"\u002Fnotes\u002Ftuning-fear-by-numbers\">Tuning fear by numbers\u003C\u002Fa> explains that system, and \u003Ca href=\"\u002Fnotes\u002Fi-wont-be-abducted-postmortem\">the jam postmortem\u003C\u002Fa> covers what got cut in 63 hours.\u003C\u002Fp>\n\u003Cp>The September update on Wavedash is where the game became readable. A placement flow that passed its tests but ate clicks in the real interface. A boss that was first too easy and then impossible to understand, until its instructions moved onto the board above his head. An economy that gave tools away. \u003Ca href=\"\u002Fnotes\u002Fi-wont-be-abducted-after-the-jam\">The follow-up postmortem\u003C\u002Fa> goes through each one.\u003C\u002Fp>\n",[572,575,578,581],{"label":199,"items":573,"variants":574},[201,202,203,204,205],[5,151],{"label":208,"items":576,"variants":577},[210,211,212,213,214,215],[5,151],{"label":230,"items":579,"variants":580},[232,233,234,235,236,237,238],[5,151],{"label":241,"items":582,"variants":583},[243,244,205,245,246,247],[5,151],[585,588,591,594,604,607,610,613,616,619,622],{"slug":259,"title":260,"dek":261,"date":262,"project":263,"tags":586,"variants":587,"order":269,"readingMinutes":270,"contentHtml":271},[265,266,151,267],[5,151],{"slug":273,"title":274,"dek":275,"date":262,"project":276,"tags":589,"variants":590,"order":74,"readingMinutes":134,"contentHtml":283},[278,279,280,281],[5,151],{"slug":285,"title":286,"dek":287,"date":288,"project":289,"tags":592,"variants":593,"order":74,"readingMinutes":296,"contentHtml":297},[265,291,292,151,293,294],[5,151],{"slug":595,"title":596,"dek":597,"date":598,"project":276,"tags":599,"variants":602,"order":74,"readingMinutes":116,"contentHtml":603},"spare-knight-ui-overhaul","Spare Knight: teaching the UI to explain itself","Players said the interface was hard to read. I rebuilt it as a token system inside Phaser and fixed the soft-locks on the way.","2026-07-01",[600,339,348,601,365],"devlog","readability",[151],"\u003Cp>\u003Cem>Spare Knight\u003C\u002Fem> shipped for Gamedev.js Jam 2026 with combat I was proud of and a UI I was not. The jam comments were kind and clear: the layout was hard to read, and a few players hit bugs that ended their runs. I replied “Game UI is not my forte, hopefully I’ll improve it in next builds,” and then went to find out whether that was true. This update is the answer, and it is the build that is live on \u003Ca href=\"http:\u002F\u002Fitch.io\">itch.io\u003C\u002Fa> now.\u003C\u002Fp>\n\u003Ch2>Start with the font\u003C\u002Fh2>\n\u003Cp>The original build used IM Fell English everywhere. It looked like a medieval manuscript, which was the point, and it read like one, which was the problem. Ability names, damage numbers, and tooltips all paid the atmosphere tax. Body text moved to Alegreya: still a warm serif that belongs in the world, but designed for reading. That one change did more for the game than any mechanic I tuned during the jam.\u003C\u002Fp>\n\u003Ch2>A token system inside a game engine\u003C\u002Fh2>\n\u003Cp>The deeper fix was structural. Text styling was scattered across scenes as ad-hoc Phaser calls, so every fix had to be applied in six places and drifted anyway. I rebuilt it the way I would build a web app: a \u003Ccode>textStyle()\u003C\u002Fcode> helper with named type roles, panel tokens for chrome, and a small atomic kit, shared atoms and a real Modal, that every scene consumes. The first bug the migration caught was in the system itself: type role objects that never resolved. Once that was fixed, restyling every non-combat scene was a sweep rather than a rewrite.\u003C\u002Fp>\n\u003Cp>If you build design systems for the web, this is the same discipline in a different renderer. Tokens are tokens.\u003C\u002Fp>\n\u003Ch2>Make the mechanics visible\u003C\u002Fh2>\n\u003Cp>The combat rules were always deterministic. The interface just kept that a secret. The readability pass put the game state on the grid itself:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Enemies are highlighted directly, with their threat range shown, instead of asking you to decode cell tints.\u003C\u002Fli>\n\u003Cli>Every status effect renders on its cell, and its tooltip is generated from the mechanics data, so the text cannot drift from the rules.\u003C\u002Fli>\n\u003Cli>Ability buttons know why they are disabled. An out-of-range ability stays armable, and a reason tooltip tells you what is wrong instead of greying out.\u003C\u002Fli>\n\u003Cli>MP costs sit on the buttons as price tags, damage numbers are signed, and hits land with real collision and an impact bounce.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>The lesson repeats from \u003Ca href=\"\u002Fnotes\u002Fthunderbashers-postmortem\">ThunderBashers\u003C\u002Fa>: readability is a mechanic. Deterministic combat only feels fair when the player can see the determinism.\u003C\u002Fp>\n\u003Ch2>The soft-locks\u003C\u002Fh2>\n\u003Cp>Two players reported turns that never ended. Both traced to the reaction system. A timing wheel could wedge in a state that never resolved, and separately, killing the turn announcer’s tween mid-flight left the turn queue waiting forever. Both were root-caused and fixed, along with a combat input crash from a stale text pool and an invisible-cursor bug that the new painted cursors replaced outright.\u003C\u002Fp>\n\u003Ch2>What carried\u003C\u002Fh2>\n\u003Cp>The whole overhaul landed as one branch, built in seven passes: typography, tooltips, ability bar, statuses, inventory counts, panel chrome, scene migration. Fixes first, then polish: fullscreen, a cursor sweep, and a Salvage and Forge layout rebuild with panels that finally line up. Player comments picked the target. The token system made hitting it cheap.\u003C\u002Fp>\n",{"slug":299,"title":300,"dek":301,"date":302,"project":303,"tags":605,"variants":606,"order":74,"readingMinutes":270,"contentHtml":308},[265,291,292,305,306],[5,151],{"slug":310,"title":311,"dek":312,"date":313,"project":11,"tags":608,"variants":609,"order":74,"readingMinutes":116,"contentHtml":322},[315,316,317,318,319,320],[5,151],{"slug":324,"title":325,"dek":326,"date":327,"project":263,"tags":611,"variants":612,"order":74,"readingMinutes":116,"contentHtml":333},[329,266,330,331],[5,151],{"slug":335,"title":336,"dek":337,"date":262,"project":303,"tags":614,"variants":615,"order":99,"readingMinutes":296,"contentHtml":341},[265,279,339,281,291,305],[5,151],{"slug":343,"title":344,"dek":345,"date":346,"project":276,"tags":617,"variants":618,"order":99,"readingMinutes":134,"contentHtml":351},[265,281,279,348,291,349],[5,151],{"slug":353,"title":354,"dek":355,"date":327,"project":263,"tags":620,"variants":621,"order":99,"readingMinutes":134,"contentHtml":359},[265,266,292,357],[5,151],{"slug":361,"title":362,"dek":363,"date":327,"project":276,"tags":623,"variants":624,"order":116,"readingMinutes":116,"contentHtml":368},[265,348,292,365,366],[5,151],"2026-09-08T06:47:57.544Z",1788850091635]