<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John</title>
    <description>The latest articles on DEV Community by John (@asx).</description>
    <link>https://dev.to/asx</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3422762%2F59ef83cf-e027-46ce-a59f-b6b771901029.png</url>
      <title>DEV Community: John</title>
      <link>https://dev.to/asx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/asx"/>
    <language>en</language>
    <item>
      <title>Duckmon: The Demo Was Ready, The Game Was Not</title>
      <dc:creator>John</dc:creator>
      <pubDate>Mon, 20 Jul 2026 20:49:24 +0000</pubDate>
      <link>https://dev.to/asx/duckmon-the-demo-was-ready-the-game-was-not-34dm</link>
      <guid>https://dev.to/asx/duckmon-the-demo-was-ready-the-game-was-not-34dm</guid>
      <description>&lt;h1&gt;
  
  
  Duckmon: The Demo Was Ready, But the Game Was Not
&lt;/h1&gt;

&lt;p&gt;It's been a long time since my last dev.to post, the 12th of February, to be exact. Back then, Duckmon was getting closer and closer to becoming demo ready. After another four or five weeks, it was right on the edge of a release. The loop worked. The systems were stable. The artwork was 99% complete and implemented. The UI flowed end to end, achievements popped, everything was working as intended, and even the server-authoritative backend was humming along with no issues.&lt;/p&gt;

&lt;p&gt;I had registration, verification, card packs, a binder, a shop, factions, and over 300 cards with full art, stats, and lore. I had player profiles, avatars, quote systems, experience and leveling systems, a full soundtrack, and full SFX.&lt;/p&gt;

&lt;p&gt;I was polishing. I was debugging. I was preparing for a real demo release not just a small vertical slice, but a full demo, fully playable, with some sections intentionally blocked off.&lt;/p&gt;

&lt;p&gt;And then something happened.&lt;/p&gt;

&lt;p&gt;I realised the demo was ready… but the game wasn't.&lt;/p&gt;

&lt;p&gt;Not in the sense of bugs or missing features, those were tamed and handled after months of solo work. It was something deeper.&lt;/p&gt;

&lt;p&gt;The loop worked, but it didn't feel alive yet. There was no excitement for me. Every developer's worst self-doubt kicked in: my game is almost finished, but it's boring. After a few matches, even I was bored.&lt;/p&gt;

&lt;p&gt;It needed more depth.&lt;br&gt;
More retention.&lt;br&gt;
More strategy.&lt;br&gt;
More "just one more match" energy.&lt;br&gt;
Some excitement.&lt;/p&gt;

&lt;p&gt;So instead of releasing the demo, I made one of the hardest decisions a solo dev can probably make: I paused the release and expanded the game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Falling Off, Then Finding My Way Back
&lt;/h2&gt;

&lt;p&gt;At the same time, I'd been neglecting everything around the project, not just the game itself. My website hadn't been updated since I made it. My Discord had been abandoned. Even here on dev.to, I'd gone quiet for six months.&lt;/p&gt;

&lt;p&gt;Eventually I neglected the project itself too. I didn't touch Unity for about six weeks or more. Supabase kept pausing my backend from lack of activity. I lost my motivation.&lt;/p&gt;

&lt;p&gt;What pulled me back was simple: I didn't want to have wasted all this time and effort. I wanted to get this project past the point where "working" is enough.&lt;/p&gt;

&lt;p&gt;Duckmon started as a minigame in the first project I ever attempted - my first time ever using Unity or writing code. Then it tried to be a game in Roblox, who weren't too happy with my art style and handed me multiple bans. So I came back to Unity, where nothing restricted me, and it became its own standalone project. After a lot of cursing, late nights, and effort, it finally started to feel like my own multiverse.&lt;/p&gt;

&lt;p&gt;When I booted the project back up to "finish the fight", the battle loop, as functional as it was, felt too shallow for the kind of game I wanted players to sink into. It felt like a cheap knockoff. A poser. An imposter.&lt;/p&gt;

&lt;p&gt;I didn't want Duckmon to be a play-once-and-forget indie game, or something people looked at and called AI slop. I wanted it to be a game people return to, a game with layers, with strategy, with its own identity. Rough, yes. Amateur, hell yeah. But mine.&lt;/p&gt;

&lt;p&gt;So I asked myself: what would make Duckmon worth playing for weeks instead of minutes? What does it need to actually feel replayable and engaging?&lt;/p&gt;

&lt;p&gt;The answer was obvious: depth, systems, strategy, choice, tactics, all balanced with a little luck and future competition. And that meant expanding the battle system far beyond the original scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Squad Battle Mode: The Big Expansion
&lt;/h2&gt;

&lt;p&gt;The first major addition was Squad Battle Mode, a full team-based battler inspired by games like Pokémon and from other RPG battle systems, plus my own obsession with tactical card combat.&lt;/p&gt;

&lt;p&gt;This wasn't just "add more cards." It was a structural reimagining, taking my existing dataset, my card databases, and figuring out how to actually use and expand what I already had.&lt;/p&gt;

&lt;p&gt;Using the cards a player has collected and their active deck, Squad Battle Mode is a 5v5 battle with active and bench units. Every card becomes a unit with 4 unique skills, HP, team energy, elemental typing, and strengths/weaknesses. On top of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;KO → auto-swap logic&lt;/li&gt;
&lt;li&gt;Graveyard tracking&lt;/li&gt;
&lt;li&gt;Team energy economy&lt;/li&gt;
&lt;li&gt;A hybrid system of turn-based and action-based status ticking&lt;/li&gt;
&lt;li&gt;Damage over time, crowd control, buffs, and debuffs&lt;/li&gt;
&lt;li&gt;Team buffs and enemy debuffs&lt;/li&gt;
&lt;li&gt;Faction synergy&lt;/li&gt;
&lt;li&gt;Real tactical decision making, with a little luck mixed in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suddenly Duckmon wasn't just a card battler. It was a team strategy game. And it felt right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Story Mode: A Second Pillar of Duckmon
&lt;/h2&gt;

&lt;p&gt;While Squad Battle Mode was growing the tactical side of the game, a completely different part of the project was growing alongside it, slower, more atmospheric, a narrative-driven aspect.&lt;/p&gt;

&lt;p&gt;Story Mode is inspired by classic choose-your-own-adventure books. Every decision leads to a new node, every node branches into two choices, and every choice pushes the player toward one of ten real endings, plus plenty of game-over endings along the way. It's not a card battler. It's backstory. It's lore you can actually walk through.&lt;/p&gt;

&lt;p&gt;Chapter one of the Electric Faction is already done — full artwork, its own soundtrack, a complete narrative arc. The rest of the faction chapters will get built out depending on how Story Mode lands in the demo.&lt;/p&gt;

&lt;p&gt;Story Mode rewards gold for card packs, but it's not required for progression. It sits alongside the other modes as something optional and atmospheric — a way to explore the world behind the cards without having to fight for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Battle Modes That Now Define Duckmon
&lt;/h2&gt;

&lt;p&gt;Duckmon now has multiple gameplay pillars working together instead of competing with each other: Story Mode for narrative depth, the original battle mode for fast matches, and Squad Battle Mode for tactical depth. Squad Battle Mode itself is planned to expand into six variants:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Classic Squad Battle Mode&lt;/strong&gt; — the core 5v5 team battler, built around a player's own cards, deck, synergies, and strategy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ranked PVP&lt;/strong&gt; — a competitive mode where players fight each other with their own squads, rewarding consistency, strategy, and mastery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Friendly PVP&lt;/strong&gt; — the casual version. No pressure, no ranking, just play.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seasonal Squad Mode&lt;/strong&gt; — a rotating format using predetermined batches of cards. One season might be Ice Faction only, another Epic rarity only, another a curated balance set. Ranked and friendly versions will exist here too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Challenge Mode&lt;/strong&gt; — fighting AI-controlled squads with predetermined cards. No deck building, no collection advantage, just pure tactical problem-solving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Squad Battles&lt;/strong&gt; — the AI brain is already built specifically for Squad Battle Mode. It reads the board state, tracks energy, evaluates threats, and makes decisions using the same rules the player does, so players can test their squads against something genuinely smart.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Building a Pokémon-Style Battler With My Existing Assets
&lt;/h2&gt;

&lt;p&gt;I didn't want to reinvent the wheel. I wanted to evolve what I already had.&lt;/p&gt;

&lt;p&gt;So I took my existing Duckmon cards : the art, the stats, the lore, and built a full Pokémon-style combat system around them, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A draft system based on the player's currently equipped deck&lt;/li&gt;
&lt;li&gt;Element types and a full type-effectiveness chart&lt;/li&gt;
&lt;li&gt;Status conditions, DOT effects, skip-turn CC, action-based penalties, turn-based durations&lt;/li&gt;
&lt;li&gt;Team energy gain rules&lt;/li&gt;
&lt;li&gt;Server-authoritative combat resolution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything had to be formula accurate, consistent, and scalable. This wasn't a minigame anymore. It was a battle engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skill System: The Heart of the Depth
&lt;/h2&gt;

&lt;p&gt;To make battles actually mean something, I needed real skills, not placeholders. So I built, and I'm still actively expanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A full skill system&lt;/li&gt;
&lt;li&gt;A CSV-driven skill database, expanded into a dual-purpose master data holder&lt;/li&gt;
&lt;li&gt;A runtime skill executor&lt;/li&gt;
&lt;li&gt;A buff/debuff manager and a status manager&lt;/li&gt;
&lt;li&gt;A DOT system and a team buff system&lt;/li&gt;
&lt;li&gt;A damage calculator, hit chance formula, and turn order formula&lt;/li&gt;
&lt;li&gt;Shield and reflect systems&lt;/li&gt;
&lt;li&gt;A battle stat recalculation pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every skill is a mix of power values, percent values, flat values, duration and tick rules, stat scaling (attack, defence, intelligence, dexterity), element synergies, rarity, status effects, team effects, and healing/cleansing/recovery/revival.&lt;/p&gt;

&lt;p&gt;I've expanded the CSV to include attack skills, support skills, DOT, CC, team buffs, debuffs, healing, shields, reflects, avoidance, energy manipulation, faction-specific moves, and mythic-tier abilities.&lt;/p&gt;

&lt;p&gt;Every Duckmon now has a real identity, not just stats, but a moveset that feels like it belongs to that specific card, built with team synergy and playstyle in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging: The Solo Dev Reality
&lt;/h2&gt;

&lt;p&gt;This part isn't glamorous, but it's real.&lt;/p&gt;

&lt;p&gt;I've spent countless nights debugging and refactoring: status stacking, duration ticking, DOT timing, energy economy, turn order, skill resolution, shield absorption, reflect damage, bench logic, KO logic, graveyard logic, swap logic, UI flow, server sync, race conditions, edge cases, null references, CSV parsing, runtime stat recalculation.&lt;/p&gt;

&lt;p&gt;Solo dev isn't just building. It's fixing. And fixing. And fixing again. And once it's all fixed (it isn't, so break it and fix it again), every fix still made the game stronger.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision to Reject Monetization
&lt;/h2&gt;

&lt;p&gt;This part matters to me.&lt;/p&gt;

&lt;p&gt;I don't want Duckmon to be a gacha trap. I don't want it to be a microtransaction machine. I don't want it to be a "pay to progress" game. I want it to be a game first.&lt;/p&gt;

&lt;p&gt;So I made the decision:&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;No monetization in the demo. No monetization in the core game loop. No monetization until the game is worth paying for — if ever, and if that happened, the only monetization would be the purchasing of the game at a cheap price anyway (to possibly cover the steam fees, if i ever get there), no micro transactions in the game ... ever *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The cards you get, from the second you get them, are yours at full potential. No grinding for duplicates, no leveling up cards. You own it, you use it, that's it.&lt;/p&gt;

&lt;p&gt;I'm building Duckmon because I wanted to build something I can be proud of , something I built alone, by myself, starting with zero coding experience, not for the money, just for the love of it. Something I can point to and say, "I built that." Not because I want to squeeze players.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Duckmon Stands Today
&lt;/h2&gt;

&lt;p&gt;Duckmon is no longer a minigame. It's no longer a prototype. It's no longer a "maybe someday" project.&lt;/p&gt;

&lt;p&gt;It's a real game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server-authoritative backend&lt;/li&gt;
&lt;li&gt;Email registration and verification&lt;/li&gt;
&lt;li&gt;Card packs, binder, shop, factions&lt;/li&gt;
&lt;li&gt;310 cards&lt;/li&gt;
&lt;li&gt;Full battle engine, skill system, status system, DOT/CC system&lt;/li&gt;
&lt;li&gt;Full team battle mode&lt;/li&gt;
&lt;li&gt;Full UI flow and full demo loop&lt;/li&gt;
&lt;li&gt;Story Mode chapter one&lt;/li&gt;
&lt;li&gt;Multiple planned Squad Battle variants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real depth. Real strategy. Real retention. Real fun.&lt;/p&gt;

&lt;p&gt;The demo isn't delayed. It's evolving. Slowly, i am getting there and i also plan to post updates here more regularly, no promises, but that is my intention. &lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>beginners</category>
      <category>csharp</category>
    </item>
    <item>
      <title>From my first Dev.to challenge to building my own website.</title>
      <dc:creator>John</dc:creator>
      <pubDate>Thu, 12 Feb 2026 10:08:12 +0000</pubDate>
      <link>https://dev.to/asx/from-my-first-devto-challenge-to-building-my-own-website-2ph7</link>
      <guid>https://dev.to/asx/from-my-first-devto-challenge-to-building-my-own-website-2ph7</guid>
      <description>&lt;p&gt;Chaos to Concept: How I Built a Solo Game Dev Site Without a Single "Hello World".&lt;/p&gt;

&lt;p&gt;We’ve all seen the "My first website" posts. Usually, they start with a FreeCodeCamp certificate or some YouTube video tutorials, basically, a "Hello World" tutorial.&lt;/p&gt;

&lt;p&gt;I didn't do any of that. I don't like being told how to build things. I like breaking things until they work. I’m a solo dev, a dad, and a husband who decided to stop playing games and start building them from scratch. No training. No prior experience. &lt;/p&gt;

&lt;p&gt;I have not posted for a while and been absorbed in real life challenges, and my game dev challenges, it has all been one crazy journey, trial by fire and lots of self learned lessons.&lt;/p&gt;

&lt;p&gt;Here is the evolution of my web dev journey, from a Dev.to challenge entry to the official home of my solo game studio and i would like to give props to Dev.to for making this even possible for me, in the sense that if i had not participated in this Halloween challenge in October 2025 , i would never have attempted to create this website.&lt;/p&gt;

&lt;p&gt;Project 1: The "TextZ Ombie" Shrine&lt;br&gt;
The Goal: A landing page for a Dev.to Halloween challenge. The Reality:&lt;a href="https://asxgtr.github.io/TextZ-Ombie-Landing-Page/" rel="noopener noreferrer"&gt;https://asxgtr.github.io/TextZ-Ombie-Landing-Page/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was my "Trial by Fire." I didn't use a tutorial; I just started throwing HTML and CSS at the wall to see what stuck, and if i am being totally honest, a lot of questions to A.I.&lt;/p&gt;

&lt;p&gt;The Vibe: Pure 90s-style chaos.&lt;/p&gt;

&lt;p&gt;The Win: I actually got audio elements working and figured out how to animate emojis to create a "horde" feel. I was proud of my first attempt although i knew it was very "amateur".&lt;/p&gt;

&lt;p&gt;The Lesson: I realized that while I could make a page "work," it didn't tell a story. It was a collection of assets, not a brand. A toe in the water. &lt;/p&gt;

&lt;p&gt;The Pivot: Becoming a Solo "Studio" (in a small sense)&lt;/p&gt;

&lt;p&gt;Between the first site and the second, my game development spiraled. My first project, TextZ Ombie, taught me about systems and UI. Even if it is temporarily on hold, 6 months of game dev from scratch taught me a lot more than any tutorial ever could, and during this development i was creating something in TextZ Ombie that deserved to be a standalone..     So then Duckmon happened, a duck powered card battler that exploded from a minigame into its own standalone multiverse. A combination of card collecting, and a card battle game inspired by the mechanics from Triple Triad (FF8).&lt;/p&gt;

&lt;p&gt;I realized I didn't just need a "page", I needed a headquarters, not a discord server, something that is my own.&lt;/p&gt;

&lt;p&gt;Project 2: Quackwork Studios&lt;br&gt;
The Goal: A professional (more so than project one), "Real &amp;amp; Raw" home for my games. The Reality: &lt;a href="https://quackworkstudios.com/" rel="noopener noreferrer"&gt;https://quackworkstudios.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where the growth really shows. I stayed true to my "No Tutorial" rule, but this time I focused on intentionality.&lt;/p&gt;

&lt;p&gt;What changed?&lt;/p&gt;

&lt;p&gt;Identity: I moved away from the neon on black chaos to a more sophisticated "Dark Mode" aesthetic that screams "Indie Dev."&lt;/p&gt;

&lt;p&gt;The Stack: I integrated things I never thought I'd touch Cloudflare, Ionos, and even Supabase/Resend for the backend contact forms. Wiring together DNS and Github and all other things i basically do not understand , until it just "worked"&lt;/p&gt;

&lt;p&gt;The Narrative: Instead of just showing assets, I’m showing a journey. The site now features a devlog, active project tracking, and a clear "About" section that explains my philosophy: A.I assisted, Solo built.&lt;/p&gt;

&lt;p&gt;The "No Tutorial" Comparison&lt;br&gt;
Website 1 (TextZ Ombie) &lt;br&gt;
Website 2 (Quackwork Studios)&lt;/p&gt;

&lt;p&gt;Why I’m Sharing This&lt;br&gt;
I see a lot of people get stuck in "tutorial hell." They’re afraid to build because they haven't finished a course yet, have not got a certificate, done the training, did the "Hello World". &lt;/p&gt;

&lt;p&gt;My advice? &lt;/p&gt;

&lt;p&gt;Just build it. My first site was a mess of emojis and raw HTML. &lt;/p&gt;

&lt;p&gt;My second site is the more professional (Lightly) face of my studio. All anyone needs is a stubborn idea and the willingness to fail until it looked right.&lt;/p&gt;

&lt;p&gt;I’m currently building Duckmon in Unity and this project is coming along fantastically right now, with a working server authoritative stance, working legit Email registration, verification and support. Card pack openings, Card Binder, a Shop, Faction screens, a 310 Card collection each with unique full card art, stats and lore. It is really not too far from a demo release state (with the need for further polishing). But more importantly it is working, like a real game.&lt;/p&gt;

&lt;p&gt;If you’re a solo dev or a "creationist" who uses AI to amplify your workflow, I’d love to connect or hear your story/stories.&lt;/p&gt;

&lt;p&gt;What was the one project that made you feel like you finally "leveled up" from a idea to a creator?&lt;/p&gt;

</description>
      <category>html</category>
      <category>webdev</category>
      <category>unity3d</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Dev Log 38 - The Christmas Resurrection (Kind - Of )</title>
      <dc:creator>John</dc:creator>
      <pubDate>Mon, 22 Dec 2025 00:48:16 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-38-the-christmas-resurrection-kind-of--2gnm</link>
      <guid>https://dev.to/asx/dev-log-38-the-christmas-resurrection-kind-of--2gnm</guid>
      <description>&lt;p&gt;🎄 Dev Log: The Christmas Resurrection&lt;br&gt;
(a.k.a. “Still No Halloween Results”)&lt;/p&gt;

&lt;p&gt;Hello again, internet. Yes, I’m alive. Yes, it’s almost Christmas. No, the Halloween Frontend Challenge results still haven’t been released. I assume the judges were eaten by CSS goblins. Happens.&lt;/p&gt;

&lt;p&gt;Anyway. I am here to talk about why I vanished.&lt;/p&gt;

&lt;p&gt;🎃 1. The Great Dev Log Hiatus&lt;br&gt;
I fully intended to keep posting dev logs. I really did.&lt;/p&gt;

&lt;p&gt;Then the challenge results never came out and my brain went:&lt;/p&gt;

&lt;p&gt;“Ah. Time to hibernate.”&lt;/p&gt;

&lt;p&gt;And so I did.&lt;/p&gt;

&lt;p&gt;🧪 2. The Mini Game That Became a Monster&lt;/p&gt;

&lt;p&gt;I started building a tiny mini‑game inside my main project.&lt;/p&gt;

&lt;p&gt;I thought it was a "mini" game anyway , some fun collectable cards , maybe play again AI in my project, just a little side thing. A distraction. A palate cleanser.&lt;/p&gt;

&lt;p&gt;Naturally, it mutated into its own ecosystem, a full blown 2nd project complete with:&lt;/p&gt;

&lt;p&gt;its own UI&lt;/p&gt;

&lt;p&gt;its own logic&lt;/p&gt;

&lt;p&gt;its own lore&lt;/p&gt;

&lt;p&gt;its own problems&lt;/p&gt;

&lt;p&gt;its own identity crisis&lt;/p&gt;

&lt;p&gt;Classic me.&lt;/p&gt;

&lt;p&gt;🟦 3. The Roblox Arc (a.k.a. My Exile)&lt;br&gt;
Then I decided to “learn Roblox scripting for fun.”&lt;/p&gt;

&lt;p&gt;Turns out roblox has it's own coding language , a version of Lua, specifically a customized version known as Luau.&lt;/p&gt;

&lt;p&gt;I had never even heard of it , but the promise of free server hosting , and taking care of all the fiddly stuff, was enough for me to give it a try.&lt;/p&gt;

&lt;p&gt;This was a mistake.&lt;/p&gt;

&lt;p&gt;Within days ( or weeks ):&lt;/p&gt;

&lt;p&gt;I got a warning&lt;/p&gt;

&lt;p&gt;Then a 1‑day ban&lt;/p&gt;

&lt;p&gt;Then a 3‑day ban&lt;/p&gt;

&lt;p&gt;All because the AI moderation system decided that any amount of visible skin is a war crime.&lt;/p&gt;

&lt;p&gt;I wasn’t even making anything weird. I was making a card game. A card Game where the main subject is ducks. Apparently elbows are forbidden, as are any ducks with 6 packs.&lt;/p&gt;

&lt;p&gt;So I left Roblox like a disgraced wizard and returned to Unity while i await the banishment, like a peasant thrown from the village.&lt;/p&gt;

&lt;p&gt;🧱 4. The Birth of This New Project&lt;/p&gt;

&lt;p&gt;Out of the ashes of my Roblox exile came this new Unity project. ( side project ) &lt;/p&gt;

&lt;p&gt;It started simple:&lt;/p&gt;

&lt;p&gt;Title screen&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjd4gsjx1qzlhzoi3os8p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjd4gsjx1qzlhzoi3os8p.png" alt=" " width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Credits panel&lt;/p&gt;

&lt;p&gt;Settings panel&lt;/p&gt;

&lt;p&gt;Then I blinked and suddenly I had:&lt;/p&gt;

&lt;p&gt;A global audio engine&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using UnityEngine;
using System.Collections;

namespace Audio
{
    public class AudioManager : MonoBehaviour
    {
        public static AudioManager Instance;

        [Header("Music")]
        public AudioClip titleTheme;
        private AudioSource musicSource;

        [Header("SFX Looping")]
        private AudioSource sfxLoopSource;

        private bool hasFadedOut = false;
        private float fadeDuration = 8f;

        void Awake()
        {
            if (Instance != null &amp;amp;&amp;amp; Instance != this)
            {
                Destroy(gameObject);
                return;
            }

            Instance = this;
            DontDestroyOnLoad(gameObject);

            // Create audio sources
            musicSource = gameObject.AddComponent&amp;lt;AudioSource&amp;gt;();
            musicSource.loop = true;
            musicSource.playOnAwake = false;

            sfxLoopSource = gameObject.AddComponent&amp;lt;AudioSource&amp;gt;();
            sfxLoopSource.loop = true;
            sfxLoopSource.playOnAwake = false;

            ApplyVolumesFromOptions();
            PlayTitleTheme();
        }

        public void ApplyVolumesFromOptions()
        {
            if (AudioOptionsManager.Instance != null)
            {
                AudioListener.volume = AudioOptionsManager.Instance.MasterVolume;
                musicSource.volume = AudioOptionsManager.Instance.MusicVolume;
                sfxLoopSource.volume = AudioOptionsManager.Instance.SFXVolume;

                Debug.Log("[AudioManager] Volumes synced from AudioOptionsManager.");
            }
        }

        public void PlayTitleTheme()
        {
            if (titleTheme != null &amp;amp;&amp;amp; !hasFadedOut)
            {
                musicSource.clip = titleTheme;
                musicSource.loop = true;
                musicSource.Play();
            }
        }

        public void StopMusic()
        {
            musicSource.Stop();
        }

        public void FadeOutMusic()
        {
            if (!hasFadedOut)
            {
                StartCoroutine(FadeOutRoutine());
            }
        }

        private IEnumerator FadeOutRoutine()
        {
            hasFadedOut = true;
            float startVolume = musicSource.volume;
            musicSource.loop = false;

            for (float t = 0; t &amp;lt; fadeDuration; t += Time.deltaTime)
            {
                musicSource.volume = Mathf.Lerp(startVolume, 0f, t / fadeDuration);
                yield return null;
            }

            musicSource.volume = 0f;
            musicSource.Stop();
        }

        public void PlayLoop(AudioClip clip, float volume = 1f)
        {
            if (clip == null)
            {
                Debug.LogWarning("[AudioManager] Tried to play null AudioClip.");
                return;
            }

            sfxLoopSource.clip = clip;
            sfxLoopSource.volume = Mathf.Clamp01(volume);
            sfxLoopSource.loop = true;
            sfxLoopSource.Play();
        }

        // Expose sources for external control
        public AudioSource MusicSource =&amp;gt; musicSource;
        public AudioSource SFXLoopSource =&amp;gt; sfxLoopSource;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using UnityEngine;
using UnityEngine.UI;
using Audio;

public class AudioOptionsManager : MonoBehaviour
{
    public static AudioOptionsManager Instance { get; private set; }

    [Header("UI")]
    [SerializeField] private Slider masterSlider;
    [SerializeField] private Slider musicSlider;
    [SerializeField] private Slider sfxSlider;
    [SerializeField] private Button resetButton;
    [SerializeField] private Button closeButton;

    [Header("Audio Sources")]
    [SerializeField] private AudioSource musicSource;
    [SerializeField] private AudioSource sfxLoopSource;

    public const float DefaultMasterVolume = 0.75f;
    public const float DefaultMusicVolume = 0.75f;
    public const float DefaultSFXVolume = 0.75f;

    private float masterVolume = DefaultMasterVolume;
    private float musicVolume = DefaultMusicVolume;
    private float sfxVolume = DefaultSFXVolume;

    public float MasterVolume =&amp;gt; masterVolume;
    public float MusicVolume =&amp;gt; musicVolume;
    public float SFXVolume =&amp;gt; sfxVolume;

    private void Awake()
    {
        Instance = this;
    }

    private void Start()
    {
        // Auto-assign sources from AudioManager
        if (AudioManager.Instance != null)
        {
            if (musicSource == null)
                musicSource = AudioManager.Instance.MusicSource;

            if (sfxLoopSource == null)
                sfxLoopSource = AudioManager.Instance.SFXLoopSource;
        }

        // Wire UI
        masterSlider.onValueChanged.AddListener(OnMasterVolumeChanged);
        musicSlider.onValueChanged.AddListener(OnMusicVolumeChanged);
        sfxSlider.onValueChanged.AddListener(OnSFXVolumeChanged);
        resetButton.onClick.AddListener(OnResetAudio);
        closeButton.onClick.AddListener(OnClosePressed);

        // Initialize sliders
        masterSlider.value = masterVolume;
        musicSlider.value = musicVolume;
        sfxSlider.value = sfxVolume;

        ApplyVolumes();
    }

    private void OnMasterVolumeChanged(float value)
    {
        masterVolume = Mathf.Clamp01(value);
        ApplyVolumes();
    }

    private void OnMusicVolumeChanged(float value)
    {
        musicVolume = Mathf.Clamp01(value);
        ApplyVolumes();
    }

    private void OnSFXVolumeChanged(float value)
    {
        sfxVolume = Mathf.Clamp01(value);
        ApplyVolumes();
    }

    private void OnResetAudio()
    {
        masterVolume = DefaultMasterVolume;
        musicVolume = DefaultMusicVolume;
        sfxVolume = DefaultSFXVolume;

        masterSlider.value = masterVolume;
        musicSlider.value = musicVolume;
        sfxSlider.value = sfxVolume;

        ApplyVolumes();
    }

    private void ApplyVolumes()
    {
        AudioListener.volume = masterVolume;

        if (musicSource != null)
            musicSource.volume = musicVolume;

        if (sfxLoopSource != null)
            sfxLoopSource.volume = sfxVolume;
    }

    private void OnClosePressed()
    {
        // MenuManager handles showing/hiding the panel
        gameObject.SetActive(false);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A clean UI architecture&lt;/p&gt;

&lt;p&gt;A proper menu flow&lt;/p&gt;

&lt;p&gt;A credits panel that actually scrolls ( growth! )&lt;/p&gt;

&lt;p&gt;Progress is faster this time because I actually know Unity now. I no longer open the editor, panic, and close it for two days.&lt;/p&gt;

&lt;p&gt;Character development.&lt;/p&gt;

&lt;p&gt;🧩 5. The PlayFab Signup&lt;/p&gt;

&lt;p&gt;At some point I also signed up for PlayFab. ( Today ) &lt;/p&gt;

&lt;p&gt;Why? Because future me will need:&lt;/p&gt;

&lt;p&gt;cloud saves&lt;/p&gt;

&lt;p&gt;player data&lt;/p&gt;

&lt;p&gt;inventory syncing&lt;/p&gt;

&lt;p&gt;maybe multiplayer&lt;/p&gt;

&lt;p&gt;maybe trading&lt;/p&gt;

&lt;p&gt;maybe a full backend economy&lt;/p&gt;

&lt;p&gt;Or maybe I just like dashboards. Hard to say.&lt;/p&gt;

&lt;p&gt;🃏 6. The 310‑Card Database&lt;/p&gt;

&lt;p&gt;Yes. I imported 310 cards into Unity.&lt;/p&gt;

&lt;p&gt;All with:&lt;/p&gt;

&lt;p&gt;names&lt;/p&gt;

&lt;p&gt;stats&lt;/p&gt;

&lt;p&gt;rarities&lt;/p&gt;

&lt;p&gt;factions&lt;/p&gt;

&lt;p&gt;lore&lt;/p&gt;

&lt;p&gt;effects&lt;/p&gt;

&lt;p&gt;spreadsheet‑driven data&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz56mywjpt84qxno6sq63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz56mywjpt84qxno6sq63.png" alt=" " width="800" height="238"&gt;&lt;/a&gt;&lt;br&gt;
This is the moment I realised:&lt;/p&gt;

&lt;p&gt;“Oh. This isn’t a mini‑project anymore.”&lt;/p&gt;

&lt;p&gt;This is a real game now.&lt;/p&gt;

&lt;p&gt;A few Examples ..&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1wvpks9gepplejldba9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1wvpks9gepplejldba9.png" alt=" " width="480" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61so73cmjyrpxnfjdsyg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61so73cmjyrpxnfjdsyg.png" alt=" " width="385" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh46mybormyk8cle95cn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh46mybormyk8cle95cn1.png" alt=" " width="477" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiis5x5jkh932v8skvkgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiis5x5jkh932v8skvkgf.png" alt=" " width="478" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and another 306 more !! ( i was still busy , sidetracked very much ) &lt;/p&gt;

&lt;p&gt;🧠 7. The Present Day&lt;br&gt;
Right now the project has:&lt;/p&gt;

&lt;p&gt;a clean title screen&lt;/p&gt;

&lt;p&gt;a working settings panel&lt;/p&gt;

&lt;p&gt;a scrolling credits panel&lt;/p&gt;

&lt;p&gt;a global audio system&lt;/p&gt;

&lt;p&gt;a database of 310 cards&lt;/p&gt;

&lt;p&gt;a PlayFab backend ready to be used&lt;/p&gt;

&lt;p&gt;a developer who is no longer afraid of Unity&lt;/p&gt;

&lt;p&gt;and a dev log that has risen from the dead&lt;/p&gt;

&lt;p&gt;🎁 8. What’s Next&lt;/p&gt;

&lt;p&gt;In no particular order:&lt;/p&gt;

&lt;p&gt;UI transitions&lt;/p&gt;

&lt;p&gt;One‑shot SFX&lt;/p&gt;

&lt;p&gt;Scene loading&lt;/p&gt;

&lt;p&gt;Card binder&lt;/p&gt;

&lt;p&gt;Card inspection&lt;/p&gt;

&lt;p&gt;Gameplay prototype&lt;/p&gt;

&lt;p&gt;More lore&lt;/p&gt;

&lt;p&gt;More chaos&lt;/p&gt;

&lt;p&gt;And maybe — just maybe — the Halloween challenge results.&lt;/p&gt;

&lt;p&gt;Merry Christmas&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftleszu6n71a4v20erxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftleszu6n71a4v20erxy.png" alt=" " width="800" height="894"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>unity3d</category>
      <category>beginners</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Dev Log 37 - Consolidated Update</title>
      <dc:creator>John</dc:creator>
      <pubDate>Sat, 15 Nov 2025 13:44:13 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-37-consolidated-update-oo7</link>
      <guid>https://dev.to/asx/dev-log-37-consolidated-update-oo7</guid>
      <description>&lt;p&gt;Consolidated Update&lt;br&gt;
Date: 15 November 2025 &lt;/p&gt;

&lt;p&gt;Hi, to anyone still reading my logs, this log entry is a consolidation of some recent personal dev logs , consolidated as a single log for myself, so not much of a fun or exciting read. &lt;/p&gt;

&lt;p&gt;Fixed discard/resurrection and opener-swap bugs; &lt;br&gt;
Hardened inventory/registry/loot flows; &lt;br&gt;
Centralized shrine-safe equip checks; &lt;br&gt;
Rewired and polished Use/Loot/Inventory/Gear UI; &lt;br&gt;
Implemented Options + Audio UI and Credits polish;&lt;br&gt;
Restored tooltip pipeline compatibility; &lt;br&gt;
Added defensive guards, logging, and tests.&lt;/p&gt;

&lt;p&gt;Discard flow: true removal of runtime instances; no resurrection from loot or saves.&lt;/p&gt;

&lt;p&gt;InventoryStateManager: unregister-by-reference, compatibility shims, TryGetLiveItemByReference.&lt;/p&gt;

&lt;p&gt;Loot pickup: always instantiate fresh runtime clones and register them before adding to inventory.&lt;/p&gt;

&lt;p&gt;Equip logic: all entry points use TryEquipInventoryItemIfSlotEmpty to prevent Hands overwrite; tooltip remains open on blocked equip.&lt;/p&gt;

&lt;p&gt;Open flow: exact-slot replacement for opened items; apply opener damage only (no removal); preserve state copy from sealed → opened item.&lt;/p&gt;

&lt;p&gt;UI robustness: defensive ExecuteInventoryAction; OpenOptionSlot UI no auto-invoke; Use/Loot/Inventory panels relabeled and rewired.&lt;/p&gt;

&lt;p&gt;Options &amp;amp; Audio UI: OptionsScene + in-game panel; ScreenFader/SceneSkipBlocker integration for Title Menu; functioning Master/Music/SFX sliders.&lt;/p&gt;

&lt;p&gt;Credits screen: new unique background, prefab-safe layout and polish.&lt;/p&gt;

&lt;p&gt;Tooltip pipeline: merged missing members into InventoryItem, ensured hydration and prefab bindings restored.&lt;/p&gt;

&lt;p&gt;Many minor bugs and runtime NREs fixed; added lots of logging / debuggers and probe points.&lt;/p&gt;

&lt;p&gt;Conversation summary (diagnosis → fixes)&lt;/p&gt;

&lt;p&gt;ItemFactory crash due to null registry and opener (knife types) disappearing when opening sealed items.&lt;/p&gt;

&lt;p&gt;Discarded items reappearing when taking loot.&lt;/p&gt;

&lt;p&gt;Tooltip fields/labels not showing after restoring backup → compile errors and prefab binding mismatches.&lt;/p&gt;

&lt;p&gt;Diagnosis and result&lt;/p&gt;

&lt;p&gt;Crash: PlayerInventoryManager.itemRegistry could be null; ItemFactory used it → NRE.&lt;/p&gt;

&lt;p&gt;Knife disappearance: ambiguous ID-based removals or incorrect slot replacement removed openers; asset-reference hydration issues allowed the wrong instance to be removed.&lt;/p&gt;

&lt;p&gt;Discard resurrection: discarded backing assets remained referenced (registry, gear.ContainedAssets or saves) and loot/pickup code reused those references instead of creating fresh instances.&lt;/p&gt;

&lt;p&gt;Tooltip regressions: InventoryItem in current project lacked fields/methods used by other advanced systems; prefab bindings inconsistent between backup and main project.&lt;/p&gt;

&lt;p&gt;Files inspected and iteratively edited&lt;/p&gt;

&lt;p&gt;ItemFactory: add null-registry guards and ensure fresh clone creation via Instantiate or ItemFactory.Create.&lt;/p&gt;

&lt;p&gt;OpenPanelUI.cs: canonical swap now uses ItemFactory.CS - Create, copies sealed→opened state, replaces exact gear slot, applies opener damage without removing opener, refreshes UI.&lt;/p&gt;

&lt;p&gt;ItemOpenerResolver.cs: verified it applies durability only; validated special-case logic (charcoal, tin can).&lt;/p&gt;

&lt;p&gt;PlayerInventoryManager.cs: added instance-based removal overloads (RemoveItem(InventoryItem), RemoveItem(Object)), InsertItemAt/ReplaceItemInPlace, TryGetGearAndIndex, GetSlotIndex; destroyed runtime clones on discard and improved logging.&lt;/p&gt;

&lt;p&gt;InventoryStateManager.cs: added UnregisterItem(InventoryItem), restored MarkItemAsEquipped overloads, TryGetLiveItemByReference, and defensive RegisterItem behavior.&lt;/p&gt;

&lt;p&gt;UsePanelIconRegistry.cs: corrected syntax, added missing keys (slice, repair) and returned clean registry.&lt;/p&gt;

&lt;p&gt;OpenOptionSlotUIManager: Initialize shows target (sealed container) and attaches single click listener (no auto invoke).&lt;/p&gt;

&lt;p&gt;ItemTooltipManager: ExecuteInventoryAction rewritten defensively; preserves attach, discard, equip, use, open, fill, pour, eat, drink flows.&lt;/p&gt;

&lt;p&gt;LootTooltipManager &amp;amp; LootButtonActionManager: patched to use TryEquipInventoryItemIfSlotEmpty and to instantiate fresh clones when taking loot.&lt;/p&gt;

&lt;p&gt;InventorySlotUI: ensured LoadItem/HasItem/GetAsset behave reliably with runtime clones.&lt;/p&gt;

&lt;p&gt;UI wiring: updated Use/Loot/Inventory/Gear button labels and logic to reflect new guard flows.&lt;/p&gt;

&lt;p&gt;Concrete code patterns applied&lt;/p&gt;

&lt;p&gt;Discard pipeline:&lt;/p&gt;

&lt;p&gt;PlayerInventoryManager.RemoveItem(instance) removes from gear.ContainedAssets, destroys runtime asset clone, RefreshInventoryState(), OnInventoryChanged, InventoryStateManager.UnregisterItem(item).&lt;/p&gt;

&lt;p&gt;Loot pickup:&lt;/p&gt;

&lt;p&gt;itemRegistry.GetCloneByID(template) + Object.Instantiate(template) → register new InventoryItem → AddItemToFirstFreeSlot(newItem, runtimeAsset).&lt;/p&gt;

&lt;p&gt;Open swap:&lt;/p&gt;

&lt;p&gt;Hydrate sealed asset reference → find exact gear slot (gear + index) → ReplaceItemInPlace(openedAsset) → ItemOpenerResolver.ApplyOpenerDamage(opener) → PlayerInventoryManager.UpdateItem(opener) → InventoryStateManager.Register/Unregister as needed.&lt;/p&gt;

&lt;p&gt;Equip:&lt;/p&gt;

&lt;p&gt;All equip calls go through PlayerEquipManager.TryEquipInventoryItemIfSlotEmpty(item). Failure shows warning and leaves tooltip open.&lt;/p&gt;

&lt;p&gt;UI safety:&lt;/p&gt;

&lt;p&gt;OpenOptionSlotUIManager shows correct target and waits for explicit click.&lt;/p&gt;

&lt;p&gt;ItemTooltipManager guards slot/asset/injectable/item presence and logs failure points.&lt;/p&gt;

&lt;p&gt;Why the final fix stopped the knife disappearing&lt;/p&gt;

&lt;p&gt;Replaced ambiguous ID-based removals with instance/slot-specific ReplaceItemInPlace; asset hydration ensures the slot contains the expected backing asset; opener damage reduces durability only and never removes the opener; this prevents accidental removal of the opener or another instance.&lt;/p&gt;

&lt;p&gt;Tooltip pipeline rescue (backup → main merge)&lt;br&gt;
Problem context&lt;/p&gt;

&lt;p&gt;Tooltip fields (descriptions, use labels, temperature, contamination) displayed in backup but not in main project.&lt;/p&gt;

&lt;p&gt;Restoring backup into main project produced compile errors because the backup InventoryItem lacked newer fields/methods current systems required.&lt;/p&gt;

&lt;p&gt;Prefab and scene bindings mismatched, causing hydration failures and CS0117/CS1061 compile errors.&lt;/p&gt;

&lt;p&gt;Root causes identified&lt;/p&gt;

&lt;p&gt;Missing InventoryItem members (UseButton1Label…UseButton5Label, TemperatureCelsius, ContaminationLevel, GetUseLabel(int)).&lt;/p&gt;

&lt;p&gt;Tooltip prefab bindings unassigned or different between projects.&lt;/p&gt;

&lt;p&gt;Some code was operating on stale prefab references instead of freshly instantiated, hydrated runtime clones.&lt;/p&gt;

&lt;p&gt;Concrete fixes applied&lt;/p&gt;

&lt;p&gt;Restored and merged missing InventoryItem fields:&lt;/p&gt;

&lt;p&gt;UseButton1Label … UseButton5Label&lt;/p&gt;

&lt;p&gt;TemperatureCelsius&lt;/p&gt;

&lt;p&gt;ContaminationLevel&lt;/p&gt;

&lt;p&gt;Added accessor GetUseLabel(int index)&lt;/p&gt;

&lt;p&gt;Ensured tooltip UI reads from hydrated InventoryItem instances (not from template assets).&lt;/p&gt;

&lt;p&gt;Added validator logs to confirm tooltip UI bindings are assigned and active at runtime.&lt;/p&gt;

&lt;p&gt;Compared backup and main project, merged required members into current InventoryItem, then updated backups with the fixed state.&lt;/p&gt;

&lt;p&gt;Why this approach worked&lt;/p&gt;

&lt;p&gt;The backup proved the core logic; missing members in the current InventoryItem made advanced systems fail. Merging required members preserved new features while restoring compatibility. Validator logs exposed null bindings so they were fixed surgically.&lt;/p&gt;

&lt;p&gt;Recommended follow-ups (TO-DO) &lt;/p&gt;

&lt;p&gt;GitHub update - Commit and tag this working state (e.g., vTooltipRelic-YYYYMMDD).&lt;/p&gt;

&lt;p&gt;Export working tooltip prefab and InventoryItem.cs as portable relics.&lt;/p&gt;

&lt;p&gt;Tests performed (representative)&lt;/p&gt;

&lt;p&gt;Discard lifecycle: discard B → loot with B picked up → created fresh instance; discarded instance never returns; save/load confirmed none-resurrection.&lt;/p&gt;

&lt;p&gt;Equip protection: attempts from loot/inventory when Hands occupied blocked and tooltip retained.&lt;/p&gt;

&lt;p&gt;Open flow: container→open and opener→open validated; no auto-selection; exact-slot replacement; opener persistence and durability decrement verified.&lt;/p&gt;

&lt;p&gt;Tooltip pipeline: restored tooltip fields displayed correctly; no compile errors after merge; prefab bindings validated at runtime.&lt;/p&gt;

&lt;p&gt;Options/Audio: Title Menu → OptionsScene via fader and blocker; in-game panel toggle now correctly , with resolved EventSystem conflicts; sliders update AudioManager live and working.&lt;/p&gt;

&lt;p&gt;UI stability: repeated interactions produced no NREs after defensive patches.&lt;/p&gt;

&lt;p&gt;Logs, probes and instrumentation added&lt;br&gt;
Ghost item detection in PlayerInventoryManager.LogGhostItems.&lt;/p&gt;

&lt;p&gt;ReplaceItemInPlace probes: log gear slot, index, expected vs actual asset reference before replacement.&lt;/p&gt;

&lt;p&gt;InventoryStateManager logs for RegisterItem, UnregisterItem, RemoveItem.&lt;/p&gt;

&lt;p&gt;ItemTooltipManager added explicit logs for missing slot/asset/injectable/item to pinpoint NRE sources.&lt;/p&gt;

&lt;p&gt;Tooltip Validator helper logs extended to confirm live bindings are assigned and active.&lt;/p&gt;

&lt;p&gt;Implementation notes &amp;amp; caveats&lt;br&gt;
Only destroy runtime clones; do not destroy template ScriptableObjects from ItemRegistry — mark runtime clones at creation or track them.&lt;/p&gt;

&lt;p&gt;InventoryStateManager is keyed by ItemID for compatibility; UnregisterItem removes by reference first then by ID fallback.&lt;/p&gt;

&lt;p&gt;Ensure persistence respects unique instance identification if you want save-level exclusion of removed instances.&lt;/p&gt;

&lt;p&gt;Audit other systems for use of InventoryStateManager.GetLiveItem for construction — they must instead use ItemFactory/Create or Instantiate templates.&lt;/p&gt;

&lt;p&gt;updated code ( personal reference )&lt;/p&gt;

&lt;p&gt;Itemtooltipmanager&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Game.Inventory;
using Game.Items;
using Game.Player;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using static UnityEditor.Progress;

public class ItemTooltipManager : MonoBehaviour
{
    public static ItemTooltipManager Instance { get; private set; }

    [Header("UI References")]
    public GameObject panel;
    public TextMeshProUGUI ItemDisplayNameText;
    public TextMeshProUGUI ItemDescriptionText;
    public TextMeshProUGUI ItemLoreTagText;
    public TextMeshProUGUI ItemWeightLabelText;
    public TextMeshProUGUI ItemWeightValueText;
    public TextMeshProUGUI ItemConditionLabelText;
    public TextMeshProUGUI ItemDurabilityValueText;
    public TextMeshProUGUI ItemUsesText;
    public TextMeshProUGUI ItemRarityText;
    public TextMeshProUGUI ItemLiquidVolumeText;
    public TextMeshProUGUI ItemGasVolumeText;

    [Header("Overlay")]
    public Image overlayImage;

    [Header("Inventory Buttons")]
    public Button Button1;
    public Button Button2;
    public Button Button3;
    public Button Button4;
    public Button Button5;

    public TextMeshProUGUI Button1Label;
    public TextMeshProUGUI Button2Label;
    public TextMeshProUGUI Button3Label;
    public TextMeshProUGUI Button4Label;
    public TextMeshProUGUI Button5Label;

    [Header("Use Panel Button Icons")]
    public Image Button1Icon;
    public Image Button2Icon;
    public Image Button3Icon;
    public Image Button4Icon;
    public Image Button5Icon;

    [Header("Panel References")]
    [SerializeField] private UsePanelManager usePanelManager;

    [Header("Liquid Info Panel")]
    public GameObject LiquidInfoPanel;
    public TextMeshProUGUI LiquidTypeText;
    public TextMeshProUGUI ContaminationValueText;
    public TextMeshProUGUI TemperatureValueText;

    private InventoryItem currentItem;
    private InventorySlotUI activeSlot;

    private Dictionary&amp;lt;int, Button&amp;gt; buttonMap;
    private Dictionary&amp;lt;int, TextMeshProUGUI&amp;gt; labelMap;
    private Dictionary&amp;lt;int, Image&amp;gt; iconMap;

    private void Awake()
    {
        Instance = this;

        buttonMap = new()
        {
            { 1, Button1 },
            { 2, Button2 },
            { 3, Button3 },
            { 4, Button4 },
            { 5, Button5 }
        };

        labelMap = new()
        {
            { 1, Button1Label },
            { 2, Button2Label },
            { 3, Button3Label },
            { 4, Button4Label },
            { 5, Button5Label }
        };

        iconMap = new()
        {
            { 1, Button1Icon },
            { 2, Button2Icon },
            { 3, Button3Icon },
            { 4, Button4Icon },
            { 5, Button5Icon }
        };

        Button1.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(1));
        Button2.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(2));
        Button3.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(3));
        Button4.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(4));
        Button5.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(5));

        SetTextVisibility(false);
        foreach (var kvp in buttonMap)
        {
            kvp.Value.gameObject.SetActive(false);
            labelMap[kvp.Key].text = "";
            iconMap[kvp.Key].enabled = false;
        }

        LiquidInfoPanel?.SetActive(false);
        ItemLiquidVolumeText?.gameObject.SetActive(false);
        ItemGasVolumeText?.gameObject.SetActive(false);
    }

    public void ShowTooltip(InventoryItem item, InventorySlotUI slot = null)
    {
        if (panel == null || item == null || !item.IsValid())
        {
            HideTooltip();
            return;
        }

        var liveItem = InventoryStateManager.Instance?.GetLiveItem(item.ItemID);
        if (liveItem != null)
        {
            item = liveItem;
            Debug.Log($"[Worm] 🧬 Live item rebound for tooltip: {item.ItemID}");
        }

        currentItem = item;
        activeSlot = slot;

        panel.SetActive(true);
        panel.transform.SetAsLastSibling();
        overlayImage?.gameObject.SetActive(false);

        SetTextVisibility(true);
        ItemDisplayNameText.text = !string.IsNullOrEmpty(item.DisplayName)
            ? item.DisplayName
            : "[Unnamed Item]";

        ItemDescriptionText.text = item.GetDescription();
        ItemLoreTagText.text = item.GetLoreTag();

        ItemWeightLabelText.text = "Weight";
        ItemWeightLabelText.color = ItemConditionResolver.GetWeightColor(item.GetWeight());
        ItemWeightValueText.text = $"{item.GetWeight():0.00} kg";
        ItemWeightValueText.color = ItemConditionResolver.GetWeightColor(item.GetWeight());

        UpdateUses(item);
        InjectInventoryButtons(item);

        bool hasLiquid = item.CurrentVolumeML &amp;gt; 0 || item.MaxVolumeML &amp;gt; 0;
        bool hasGas = item.CurrentGasVolumeML &amp;gt; 0 || item.MaxGasVolumeML &amp;gt; 0;

        if (hasLiquid)
        {
            ItemLiquidVolumeText.text = $"Volume {item.CurrentVolumeML}/{item.MaxVolumeML}ml";
            ItemLiquidVolumeText.gameObject.SetActive(true);

            if (item.CurrentVolumeML &amp;gt; 0)
            {
                LiquidInfoPanel?.SetActive(true);
                LiquidTypeText.text = item.CurrentLiquidType ?? "";
                ContaminationValueText.text = item.ContaminationTag ?? "";
                TemperatureValueText.text = item.LiquidTemperatureCelsius &amp;gt; 0
                    ? $"{item.LiquidTemperatureCelsius:0.#}°C"
                    : "";
            }
            else
            {
                LiquidInfoPanel?.SetActive(false);
            }
        }
        else
        {
            ItemLiquidVolumeText.text = "";
            ItemLiquidVolumeText.gameObject.SetActive(false);
            LiquidInfoPanel?.SetActive(false);
        }

        if (hasGas)
        {
            ItemGasVolumeText.text = $"Volume {item.CurrentGasVolumeML}/{item.MaxGasVolumeML}ml";
            ItemGasVolumeText.gameObject.SetActive(true);
        }
        else
        {
            ItemGasVolumeText.text = "";
            ItemGasVolumeText.gameObject.SetActive(false);
        }

        Debug.Log($"[Worm] 🧪 Tooltip data: Liquid={item.CurrentVolumeML}/{item.MaxVolumeML}, Gas={item.CurrentGasVolumeML}/{item.MaxGasVolumeML}");
    }

    public void RebindTooltip(InventoryItem item, InventorySlotUI slot)
    {
        if (item == null || slot == null || string.IsNullOrEmpty(item.ItemID))
        {
            Debug.LogWarning("[Worm] ❌ RebindTooltip failed.");
            return;
        }

        currentItem = item;
        activeSlot = slot;
        ShowTooltip(item, slot);
        Debug.Log($"[Worm] 🔁 Tooltip rebound for item: {item.ItemID}, slot: {slot.name}");
    }
    private void ExecuteInventoryAction(int slot)
    {
        if (currentItem == null)
        {
            Debug.LogWarning("[Tenchu] currentItem is null.");
            return;
        }

        string action = currentItem.GetButtonLabel(slot, "inventory");
        if (string.IsNullOrEmpty(action))
        {
            Debug.LogWarning("[Tenchu] Action label is empty.");
            return;
        }

        Debug.Log($"[Tenchu] Slot {slot} triggered action: {action}");

        switch (action.ToLowerInvariant())
        {
            case "attach":
                UniversalAttachmentManager.Instance?.TryAttach(currentItem.assetReference);
                HideTooltip();
                break;

            case "discard":
                if (currentItem == null)
                {
                    Debug.LogWarning("[Tenchu] ❌ Discard failed: currentItem is null.");
                    return;
                }

                bool removed = false;

                // Prefer removal by live instance if possible
                if (PlayerInventoryManager.Instance != null)
                {
                    removed = PlayerInventoryManager.Instance.RemoveItem(currentItem);
                    if (!removed)
                        removed = PlayerInventoryManager.Instance.RemoveItem(currentItem.ItemID);
                }
                else
                {
                    Debug.LogWarning("[Tenchu] ❌ Discard failed: PlayerInventoryManager.Instance is null.");
                }

                // Ensure global registry no longer references the instance
                if (InventoryStateManager.Instance != null)
                {
                    InventoryStateManager.Instance.UnregisterItem(currentItem);
                }
                else
                {
                    Debug.LogWarning("[Tenchu] ⚠️ InventoryStateManager.Instance is null; cannot unregister item.");
                }

                if (removed)
                {
                    if (activeSlot != null)
                    {
                        activeSlot.ClearSlot();
                        Debug.Log($"[Tenchu] 🧼 Cleared slot: {activeSlot.name}");
                    }

                    HideTooltip();
                    Debug.Log($"[Tenchu] 🗑️ Discarded item: {currentItem.ItemID}");
                }
                else
                {
                    Debug.LogWarning($"[Tenchu] ⚠️ Failed to remove item '{currentItem.ItemID}' from PlayerInventoryManager.");
                    // Still hide tooltip and attempt UI clear to avoid stale UI state
                    if (activeSlot != null)
                    {
                        activeSlot.ClearSlot();
                        Debug.Log($"[Tenchu] 🧼 Cleared slot (fallback): {activeSlot.name}");
                    }
                    HideTooltip();
                }
                break;

            case "equip":
                bool equipped = PlayerEquipManager.Instance?.TryEquipInventoryItemIfSlotEmpty(currentItem) ?? false;
                if (!equipped)
                {
                    Debug.LogWarning($"[Tooltip] 🚫 Equip blocked for '{currentItem.ItemID}' — slot may be occupied.");
                    PlayerEquipManager.Instance?.ShowEquipWarning("Hands slot is occupied. Unequip first.");
                }
                HideTooltip();
                break;

            case "use":
            case "fill":
            case "empty":
            case "combine":
            case "place":
            case "pour":
            case "eat":
            case "drink":
                usePanelManager?.OpenPanel(currentItem, activeSlot);
                break;

            case "open":
                Debug.Log($"[Tenchu] 🧿 Opening ritual triggered for '{currentItem.ItemID}'");
                OpenPanelManager.Instance?.ShowOpenersFor(currentItem);
                break;

            default:
                Debug.LogWarning($"[Tenchu] Unhandled action: {action}");
                break;
        }
    }

    private void InjectInventoryButtons(InventoryItem item)
    {
        foreach (var kvp in buttonMap)
        {
            kvp.Value.gameObject.SetActive(false);
            labelMap[kvp.Key].text = "";
            iconMap[kvp.Key].enabled = false;
        }

        SetButton(1, item.InventoryButton1Label);
        SetButton(2, item.InventoryButton2Label);
        SetButton(3, item.InventoryButton3Label);
        SetButton(4, item.InventoryButton4Label);
        SetButton(5, item.InventoryButton5Label);
    }

    private void SetButton(int slot, string labelText)
    {
        bool hasLabel = !string.IsNullOrEmpty(labelText);
        buttonMap[slot].gameObject.SetActive(hasLabel);
        labelMap[slot].text = hasLabel ? labelText : "";

        if (iconMap.TryGetValue(slot, out var icon))
        {
            var sprite = UsePanelIconRegistry.GetIcon(labelText);
            icon.sprite = sprite;
            icon.enabled = sprite != null;
        }
    }

    public void UpdateUses(InventoryItem item)
    {
        string conditionLabel = item.GetConditionLabel();
        Color conditionColor = ItemConditionResolver.GetConditionColorByDurability(item.Durability);

        ItemConditionLabelText.text = $"Condition: {conditionLabel}";
        ItemConditionLabelText.color = conditionColor;

        ItemDurabilityValueText.text = $"Durability: {item.Durability}/100";
        ItemDurabilityValueText.color = conditionColor;

        if (item.ZeroUseMode == ZeroUseMode.UntilDurabilityZero)
        {
            ItemUsesText.gameObject.SetActive(false);
        }
        else if (item.RemainingUses &amp;gt; 0)
        {
            float percent = item.MaxUses &amp;gt; 0 ? (float)item.RemainingUses / item.MaxUses : 0f;
            Color useColor = ItemConditionResolver.GetUseColorByPercentage(percent);

            ItemUsesText.text = $"Uses: {item.RemainingUses}";
            ItemUsesText.color = useColor;
            ItemUsesText.gameObject.SetActive(true);
        }
        else
        {
            switch (item.ZeroUseMode)
            {
                case ZeroUseMode.Unlimited:
                    ItemUsesText.text = "Uses: Unlimited";
                    ItemUsesText.color = ItemConditionResolver.GetUseColorByPercentage(1.0f);
                    ItemUsesText.gameObject.SetActive(true);
                    break;
                case ZeroUseMode.Zero:
                    ItemUsesText.text = "Uses: 0";
                    ItemUsesText.color = ItemConditionResolver.GetUseColorByPercentage(0f);
                    ItemUsesText.gameObject.SetActive(true);
                    break;
                default:
                    ItemUsesText.gameObject.SetActive(false);
                    break;
            }
        }

        if (!string.IsNullOrEmpty(item.Rarity))
        {
            ItemRarityText.text = $"Rarity: {item.Rarity}";
            ItemRarityText.color = GetColorForRarity(item.Rarity);
        }
    }

    private Color GetColorForRarity(string rarity)
    {
        return rarity switch
        {
            "Common" =&amp;gt; Color.white,
            "Uncommon" =&amp;gt; new Color(0.3f, 1.0f, 0.3f),
            "Rare" =&amp;gt; new Color(0.3f, 0.5f, 1.0f),
            "Epic" =&amp;gt; new Color(0.6f, 0.2f, 0.8f),
            "Legendary" =&amp;gt; new Color(1.0f, 0.84f, 0.0f),
            _ =&amp;gt; Color.white
        };
    }

    public void HideTooltip()
    {
        Debug.Log($"[Worm] 🧹 Tooltip hidden. Clearing slot: {(activeSlot != null ? activeSlot.name : "null")}");
        currentItem = null;
        activeSlot = null;
        SetTextVisibility(false);

        foreach (var kvp in buttonMap)
        {
            kvp.Value.gameObject.SetActive(false);
            labelMap[kvp.Key].text = "";
            iconMap[kvp.Key].enabled = false;
        }

        ItemLiquidVolumeText.text = "";
        ItemLiquidVolumeText.gameObject.SetActive(false);

        ItemGasVolumeText.text = "";
        ItemGasVolumeText.gameObject.SetActive(false);

        LiquidInfoPanel?.SetActive(false);
        panel?.SetActive(false);
    }

    private void SetTextVisibility(bool visible)
    {
        ItemDisplayNameText.enabled = visible;
        ItemDescriptionText.enabled = visible;
        ItemLoreTagText.enabled = visible;
        ItemWeightLabelText.enabled = visible;
        ItemWeightValueText.enabled = visible;
        ItemConditionLabelText.enabled = visible;
        ItemDurabilityValueText.enabled = visible;
        ItemUsesText.enabled = visible;
        ItemRarityText.enabled = visible;
        ItemLiquidVolumeText.enabled = visible;
        ItemGasVolumeText.enabled = visible;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inventorystatemanager&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Game.Inventory;
using System.Collections.Generic;
using UnityEngine;

public class InventoryStateManager : MonoBehaviour
{
    public static InventoryStateManager Instance { get; private set; }

    // Keyed by item.ItemID for backwards compatibility, but we also support removal by reference.
    private Dictionary&amp;lt;string, InventoryItem&amp;gt; liveItems = new();

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            Debug.Log("[Worm] 🧬 InventoryStateManager initialized.");
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void RegisterItem(InventoryItem item)
    {
        if (item == null || string.IsNullOrEmpty(item.ItemID))
        {
            Debug.LogWarning("[Worm] ❌ Attempted to register null or invalid item.");
            return;
        }

        // If there's already a registered instance for this ItemID, warn if it's a different reference.
        if (liveItems.TryGetValue(item.ItemID, out var existing))
        {
            if (!ReferenceEquals(existing, item))
            {
                Debug.LogWarning($"[Worm] ⚠️ RegisterItem: replacing existing registered instance for ID '{item.ItemID}'. Existing instance will be overwritten.");
            }
        }

        liveItems[item.ItemID] = item;
        Debug.Log($"[Worm] ✅ Registered item: {item.ItemID} | Volume={item.CurrentVolumeML} | LiquidType={item.CurrentLiquidType}");
    }

    public bool IsItemRegistered(string itemID)
    {
        return !string.IsNullOrEmpty(itemID) &amp;amp;&amp;amp; liveItems.ContainsKey(itemID);
    }

    public InventoryItem GetLiveItem(string itemID)
    {
        if (string.IsNullOrEmpty(itemID))
        {
            Debug.LogWarning("[Worm] ❌ GetLiveItem called with null or empty ID.");
            return null;
        }

        if (liveItems.TryGetValue(itemID, out var item))
        {
            Debug.Log($"[Worm] 🧪 Retrieved live item: {itemID} | Volume={item.CurrentVolumeML} | LiquidType={item.CurrentLiquidType}");
            return item;
        }

        // Silent null is fine for normal flow; keep warning for debug visibility
        Debug.LogWarning($"[Worm] ⚠️ No live item found for ID: {itemID}");
        return null;
    }

    // Restore compatibility: mark a registered item as equipped (by ID)
    public void MarkItemAsEquipped(string itemID)
    {
        if (string.IsNullOrEmpty(itemID))
        {
            Debug.LogWarning("[Worm] ❌ MarkItemAsEquipped called with null or empty ID.");
            return;
        }

        if (liveItems.TryGetValue(itemID, out var item))
        {
            item.IsEquipped = true;
            Debug.Log($"[Worm] 🧬 Item '{itemID}' marked as equipped.");
        }
        else
        {
            Debug.LogWarning($"[Worm] ❌ Item '{itemID}' not found in registry when marking equipped.");
        }
    }

    // Overload: mark as equipped by instance reference
    public void MarkItemAsEquipped(InventoryItem item)
    {
        if (item == null)
        {
            Debug.LogWarning("[Worm] ❌ MarkItemAsEquipped called with null item.");
            return;
        }

        // Prefer reference-based lookup
        if (TryGetLiveItemByReference(item, out var registered))
        {
            registered.IsEquipped = true;
            Debug.Log($"[Worm] 🧬 Item '{registered.ItemID}' marked as equipped by reference.");
            return;
        }

        // Fallback by ID
        MarkItemAsEquipped(item.ItemID);
    }

    // New: Unregister by InventoryItem instance (preferred)
    public void UnregisterItem(InventoryItem item)
    {
        if (item == null)
        {
            Debug.LogWarning("[Worm] ❌ UnregisterItem called with null.");
            return;
        }

        // Try remove by reference first
        string keyToRemove = null;
        foreach (var kvp in liveItems)
        {
            if (ReferenceEquals(kvp.Value, item))
            {
                keyToRemove = kvp.Key;
                break;
            }
        }

        if (!string.IsNullOrEmpty(keyToRemove))
        {
            liveItems.Remove(keyToRemove);
            Debug.Log($"[Worm] 🗑️ Unregistered live item by reference: {keyToRemove}");
            return;
        }

        // Fallback: remove by matching ItemID if present
        if (!string.IsNullOrEmpty(item.ItemID) &amp;amp;&amp;amp; liveItems.Remove(item.ItemID))
        {
            Debug.Log($"[Worm] 🗑️ Unregistered live item by ID fallback: {item.ItemID}");
            return;
        }

        Debug.LogWarning($"[Worm] ⚠️ UnregisterItem: item '{item.ItemID}' not found in registry.");
    }

    // Keep RemoveItem by ID for callers that only have itemID
    public void RemoveItem(string itemID)
    {
        if (string.IsNullOrEmpty(itemID))
        {
            Debug.LogWarning("[Worm] ❌ RemoveItem called with null or empty ID.");
            return;
        }

        if (liveItems.Remove(itemID))
        {
            Debug.Log($"[Worm] 🗑️ Removed item: {itemID} from live registry.");
        }
        else
        {
            Debug.LogWarning($"[Worm] ⚠️ Item '{itemID}' not found in live registry.");
        }
    }

    public Dictionary&amp;lt;string, InventoryItem&amp;gt; GetAllLiveItems()
    {
        return liveItems;
    }

    public void ClearAll()
    {
        liveItems.Clear();
        Debug.Log("[Worm] 🧹 InventoryStateManager cleared all live items.");
    }

    // Helper: attempt to find a live item by exact instance reference
    public bool TryGetLiveItemByReference(InventoryItem instance, out InventoryItem found)
    {
        found = null;
        if (instance == null) return false;
        foreach (var kvp in liveItems)
        {
            if (ReferenceEquals(kvp.Value, instance))
            {
                found = kvp.Value;
                return true;
            }
        }
        return false;
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usepanelmanager&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Game.Inventory;
using Game.Player;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using Game.UI;

public class UsePanelManager : MonoBehaviour
{
    public static UsePanelManager Instance { get; private set; }

    public bool IsOpen =&amp;gt; panelRoot != null &amp;amp;&amp;amp; panelRoot.activeInHierarchy;

    [Header("Panel Root")]
    [SerializeField] private GameObject panelRoot;

    [Header("UI References")]
    [SerializeField] private TMP_Text TitleText;
    [SerializeField] private Image BlackoutImage;
    [SerializeField] private Image ItemIcon;
    [SerializeField] private TMP_Text ItemName;

    [Header("Buttons")]
    [SerializeField] private Button Button2;
    [SerializeField] private Button Button3;
    [SerializeField] private Button Button4;
    [SerializeField] private Button Button5;
    [SerializeField] private Button Button6;

    [Header("Labels")]
    [SerializeField] private TMP_Text Button2Label;
    [SerializeField] private TMP_Text Button3Label;
    [SerializeField] private TMP_Text Button4Label;
    [SerializeField] private TMP_Text Button5Label;
    [SerializeField] private TMP_Text Button6Label;

    [Header("Icons")]
    [SerializeField] private Image Button2Icon;
    [SerializeField] private Image Button3Icon;
    [SerializeField] private Image Button4Icon;
    [SerializeField] private Image Button5Icon;
    [SerializeField] private Image Button6Icon;

    [Header("Use Sprite Registry")]
    [SerializeField] private UseSpriteRegistry useSpriteRegistry;

    [Header("Attachment Panel")]
    [SerializeField] private UniversalAttachmentPanel attachmentPanel;

    [Header("Story Node")]
    [SerializeField] private StoryNodeLoader storyNodeLoader;

    [Header("Close Button")]
    [SerializeField] private Button closeButton;

    private List&amp;lt;Button&amp;gt; buttons;
    private List&amp;lt;TMP_Text&amp;gt; labels;
    private List&amp;lt;Image&amp;gt; icons;

    private InventorySlotUI currentSlot;

    private void Awake()
    {
        Instance = this;
        buttons = new List&amp;lt;Button&amp;gt; { Button2, Button3, Button4, Button5, Button6 };
        labels = new List&amp;lt;TMP_Text&amp;gt; { Button2Label, Button3Label, Button4Label, Button5Label, Button6Label };
        icons = new List&amp;lt;Image&amp;gt; { Button2Icon, Button3Icon, Button4Icon, Button5Icon, Button6Icon };

        if (BlackoutImage != null)
            BlackoutImage.gameObject.SetActive(false);
    }

    public void OpenPanel(InventoryItem item, InventorySlotUI slot)
    {
        if (slot == null || item == null) return;

        if (panelRoot.activeInHierarchy)
        {
            HidePanel();
        }

        currentSlot = slot;

        panelRoot.SetActive(true);
        panelRoot.transform.localScale = Vector3.one;
        panelRoot.transform.SetAsLastSibling();

        var cg = panelRoot.GetComponent&amp;lt;CanvasGroup&amp;gt;();
        if (cg != null)
        {
            cg.alpha = 1f;
            cg.interactable = true;
            cg.blocksRaycasts = true;
        }

        HideAllButtons();
        UpdateItemDisplay(item);

        if (TitleText != null)
            TitleText.text = "Item Use Menu";

        if (BlackoutImage != null)
            BlackoutImage.gameObject.SetActive(true);

        // ✅ Only show contextual use actions (not Attach/Equip)
        for (int i = 1; i &amp;lt;= 5; i++)
        {
            string label = item.GetUseLabel(i);
            if (!string.IsNullOrEmpty(label) &amp;amp;&amp;amp; i - 1 &amp;lt; buttons.Count)
            {
                if (label.Equals("Attach", System.StringComparison.OrdinalIgnoreCase) ||
                    label.Equals("Equip", System.StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                Sprite icon = GetIconForLabel(label);

                UnityEngine.Events.UnityAction action = () =&amp;gt;
                {
                    Debug.Log($"[UsePanelManager] Action triggered: {label} for item {item.ItemID}");

                    switch (label.ToLowerInvariant())
                    {
                        case "open":
                            if (ItemOpenerResolver.IsOpenerTool(item))
                            {
                                // Tool path → show containers, then close
                                OpenPanelManager.Instance?.ShowOpenersFor(item);
                                HidePanel();
                            }
                            else
                            {
                                // Container path → show tools, keep panel open
                                OpenPanelManager.Instance?.ShowOpenersFor(item);
                            }
                            break;

                        case "slice":
                            Debug.Log($"[UsePanelManager] Slice action placeholder for {item.ItemID}");
                            HidePanel();
                            break;

                        case "repair":
                            Debug.Log($"[UsePanelManager] Repair action placeholder for {item.ItemID}");
                            HidePanel();
                            break;

                        case "fill":
                        case "empty":
                        case "combine":
                        case "place":
                        case "pour":
                        case "eat":
                        case "drink":
                            InventoryStateManager.Instance?.RegisterItem(item);
                            HidePanel();
                            break;

                        default:
                            Debug.LogWarning($"[UsePanelManager] Unhandled action: {label}");
                            HidePanel();
                            break;
                    }
                };

                ShowButton(i - 1, label, icon, action);
            }
        }
    }

    private void HideAllButtons()
    {
        foreach (var btn in buttons)
        {
            if (btn != null)
            {
                btn.gameObject.SetActive(false);
                btn.onClick.RemoveAllListeners();
            }
        }

        foreach (var icon in icons)
        {
            if (icon != null)
            {
                icon.sprite = null;
                icon.gameObject.SetActive(false);
            }
        }
    }

    private void UpdateItemDisplay(InventoryItem item)
    {
        if (ItemIcon != null)
            ItemIcon.sprite = item?.Icon;

        if (ItemName != null)
            ItemName.text = item?.DisplayName ?? "Unknown Item";
    }

    private void ShowButton(int index, string label, Sprite icon, UnityEngine.Events.UnityAction action)
    {
        if (index &amp;lt; 0 || index &amp;gt;= buttons.Count) return;

        var btn = buttons[index];
        var lbl = labels[index];
        var icn = icons[index];

        if (btn != null)
        {
            btn.gameObject.SetActive(true);
            btn.onClick.RemoveAllListeners();
            btn.onClick.AddListener(action);
        }

        if (lbl != null)
            lbl.text = label;

        if (icn != null)
        {
            icn.sprite = icon;
            icn.enabled = icon != null;
            icn.gameObject.SetActive(icon != null);
        }
    }

    private Sprite GetIconForLabel(string label)
    {
        if (useSpriteRegistry == null || string.IsNullOrEmpty(label)) return null;
        return useSpriteRegistry.GetSprite(label);
    }

    public void HidePanel()
    {
        if (panelRoot != null)
        {
            panelRoot.SetActive(false);

            var cg = panelRoot.GetComponent&amp;lt;CanvasGroup&amp;gt;();
            if (cg != null)
            {
                cg.blocksRaycasts = false;
                cg.interactable = false;
            }
        }

        if (BlackoutImage != null)
            BlackoutImage.gameObject.SetActive(false);

        currentSlot = null;

        ItemTooltipManager.Instance?.HideTooltip();
    }

    public void ManualClosePanel()
    {
        Debug.Log("[UsePanelManager] 🧹 ManualClosePanel triggered via inspector button");
        HidePanel();
    }

    public void ForceClosePanel()
    {
        Debug.Log("[UsePanelManager] 🧹 ForceClosePanel invoked — forcibly collapsing panel");

        try
        {
            if (panelRoot != null)
            {
                panelRoot.SetActive(false);

                var cg = panelRoot.GetComponent&amp;lt;CanvasGroup&amp;gt;();
                if (cg != null)
                {
                    cg.alpha = 0f;
                    cg.interactable = false;
                    cg.blocksRaycasts = false;
                }
            }

            if (BlackoutImage != null)
                BlackoutImage.gameObject.SetActive(false);

            currentSlot = null;

            ItemTooltipManager.Instance?.HideTooltip();

            Debug.Log("[UsePanelManager] ✅ Panel forcibly closed and state reset");
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"[UsePanelManager] ❌ ForceClosePanel failed: {ex.Message}");
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>devjournal</category>
      <category>beginners</category>
      <category>unity3d</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Dev Log 36 - Mythic Pause</title>
      <dc:creator>John</dc:creator>
      <pubDate>Sat, 08 Nov 2025 16:28:46 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-36-mythic-pause-4apk</link>
      <guid>https://dev.to/asx/dev-log-36-mythic-pause-4apk</guid>
      <description>&lt;p&gt;🧊 🌋 ❄️ ✈️ ⚔️ 9-Day Break: The Mythic Pause&lt;/p&gt;

&lt;p&gt;A personal reflection and journal log&lt;/p&gt;

&lt;p&gt;🌍 Icelandic Interlude: The First Real Break&lt;br&gt;
I travelled to the beautiful country of Iceland — a land of glaciers, volcanoes, and mythic quiet. It felt like stepping into another world. My first real vacation, and it was everything I hoped for:&lt;/p&gt;

&lt;p&gt;A slower, more intentional pace of life&lt;/p&gt;

&lt;p&gt;Surprisingly delicious food (despite the warnings)&lt;/p&gt;

&lt;p&gt;Friendly locals and a calm atmosphere, even with tourists&lt;/p&gt;

&lt;p&gt;Expensive, yes — but worth every krona&lt;/p&gt;

&lt;p&gt;After seven days of awe, I returned home and took two days to re-sync with reality. Then I reopened my survival engine project — and it felt like deciphering someone else’s code.&lt;/p&gt;

&lt;p&gt;🧭 Reentry Ritual: Reforging Flow&lt;br&gt;
I eased back in slowly, rekindling my dev rituals:&lt;/p&gt;

&lt;p&gt;Reviewed commits and journal entries&lt;/p&gt;

&lt;p&gt;Revisited ItemTooltipManager.cs and InventoryStateManager.cs&lt;/p&gt;

&lt;p&gt;Verified tooltip accuracy and runtime truth&lt;/p&gt;

&lt;p&gt;Rebound live item data and refreshed UI bindings&lt;/p&gt;

&lt;p&gt;Reconnected with my discard ritual sabotage&lt;/p&gt;

&lt;p&gt;🧨 Sabotage Discovered: False Discard Ritual&lt;br&gt;
❌ Issue&lt;br&gt;
Pressing the discard button triggered the use panel instead of purging the item.&lt;/p&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;p&gt;Use panel opened on discard&lt;/p&gt;

&lt;p&gt;Item visually disappeared but wasn’t truly deleted&lt;/p&gt;

&lt;p&gt;Discarded items reappeared after looting&lt;/p&gt;

&lt;p&gt;🔧 Fixes Applied: The True Purge Path&lt;br&gt;
Separated discard logic in ExecuteInventoryAction()&lt;/p&gt;

&lt;p&gt;Removed "discard" from use panel logic&lt;/p&gt;

&lt;p&gt;Added null guards and slot cleanup&lt;/p&gt;

&lt;p&gt;Forged RemoveItem() in InventoryStateManager&lt;/p&gt;

&lt;p&gt;Added RemoveItemByID() in PlayerInventoryManager&lt;/p&gt;

&lt;p&gt;Called activeSlot.ClearSlot() to purge lingering UI&lt;/p&gt;

&lt;p&gt;Verified resurrection trap → loot panel was rehydrating from ContainedAssets&lt;/p&gt;

&lt;p&gt;Purged item from all sources&lt;/p&gt;

&lt;p&gt;✅ Outcome: Ritual Sealed&lt;br&gt;
Discard now fully deletes item across all systems&lt;/p&gt;

&lt;p&gt;No false panel summons&lt;/p&gt;

&lt;p&gt;No ghost sprites&lt;/p&gt;

&lt;p&gt;No resurrection on loot&lt;/p&gt;

&lt;p&gt;Console logs confirm true purge&lt;/p&gt;

&lt;p&gt;🧪 Debug Tags&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Tenchu] 🗑️ Discarded item: {itemID}  
[Unicorn] 🧹 Removed item '{itemID}' from gear slot  
[Worm] 🧬 Removed item: {itemID} from live registry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧤 Equip Ritual Reforged: The Hands Transfer Purge&lt;br&gt;
❌ Issue&lt;br&gt;
Equipped items were appearing in both the hands slot and the inventory — violating runtime truth.&lt;/p&gt;

&lt;p&gt;Symptoms:&lt;/p&gt;

&lt;p&gt;Item appeared in hands and inventory simultaneously&lt;/p&gt;

&lt;p&gt;Unequipping duplicated the item&lt;/p&gt;

&lt;p&gt;InventoryStateManager and gear slots retained stale references&lt;/p&gt;

&lt;p&gt;🔧 Fixes Applied: The True Equip Path&lt;br&gt;
Purged from InventoryStateManager → RemoveItem(itemID)&lt;/p&gt;

&lt;p&gt;Purged from PlayerInventoryManager → RemoveItem(itemID)&lt;/p&gt;

&lt;p&gt;Cleared from UI slot → slot.GetItem()?.ItemID == item.ItemID → slot.ClearSlot()&lt;/p&gt;

&lt;p&gt;Injected into hands slot → GearSlotUI.RefreshSlotWithInventoryItem(item)&lt;/p&gt;

&lt;p&gt;Verified equip flow in EquipInventoryItem() → All purges occur before injection&lt;/p&gt;

&lt;p&gt;✅ Outcome: Ritual Sealed&lt;br&gt;
Item now transfers cleanly from inventory to hands&lt;/p&gt;

&lt;p&gt;No duplication on unequip&lt;/p&gt;

&lt;p&gt;No ghost sprites or registry remnants&lt;/p&gt;

&lt;p&gt;Console confirms full purge and equip&lt;/p&gt;

&lt;p&gt;🧪 Debug Tags&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[EquipManager] 🧼 Removed '{itemID}' from InventoryStateManager  
[EquipManager] 🧼 Removed '{itemID}' from PlayerInventoryManager  
[EquipManager] 🧹 Cleared inventory UI slot for '{itemID}'  
[EquipManager] 🧤 InventoryItem equipped to slot 'Hands' → {itemID}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📜 Relic Logged: The Hands Transfer Purge Ritual&lt;br&gt;
Restores runtime truth by ensuring equipped items are fully purged from inventory systems before injection into hands.&lt;/p&gt;

&lt;p&gt;🧠 Dev Mindset: From Fog to Flow&lt;br&gt;
The first few hours felt like wandering through someone else's shrine. My keyboard was unfamiliar, my systems distant. But each debug tag was a breadcrumb. By the time I traced the discard sabotage, I was fully reawakened. The rituals returned. The myth resumed.&lt;/p&gt;

&lt;p&gt;🧪 Test Rituals&lt;br&gt;
Equipped item from slot 3 → verified removal from UI and registry&lt;/p&gt;

&lt;p&gt;Unequipped item → confirmed reappearance in first free slot&lt;/p&gt;

&lt;p&gt;Discarded item → confirmed no resurrection on loot&lt;/p&gt;

&lt;p&gt;Attempted equip with no free slot → verified graceful failure&lt;/p&gt;

&lt;p&gt;Rebound tooltip after contamination → confirmed live data accuracy&lt;/p&gt;

&lt;p&gt;🧰 Ritual Hooks&lt;br&gt;
ExecuteInventoryAction() → Entry point for discard and equip logic&lt;/p&gt;

&lt;p&gt;RemoveItem() → Purges item from registry and gear slots&lt;/p&gt;

&lt;p&gt;ClearSlot() → Wipes UI remnants&lt;/p&gt;

&lt;p&gt;EquipInventoryItem() → Seals the hands transfer ritual&lt;/p&gt;

&lt;p&gt;RefreshSlotWithInventoryItem() → Injects item into hands slot&lt;/p&gt;

&lt;p&gt;📈 Impact&lt;br&gt;
Enables true equip/unequip cycles&lt;/p&gt;

&lt;p&gt;Prevents inventory overflow and ghost duplication&lt;/p&gt;

&lt;p&gt;Strengthens tooltip accuracy and runtime clarity&lt;/p&gt;

&lt;p&gt;Paves the way for equip swap fallback and slot registry automation&lt;/p&gt;

&lt;p&gt;🧩 Open Threads&lt;br&gt;
Tooltip overlay cleanup on unequip&lt;/p&gt;

&lt;p&gt;Equip swap fallback logic&lt;/p&gt;

&lt;p&gt;Slot registry automation&lt;/p&gt;

&lt;p&gt;Ghost item detection on scene reload&lt;/p&gt;

&lt;p&gt;🔮 Reflection &amp;amp; Reconnection&lt;br&gt;
This return wasn’t just technical — it was emotional. I reconnected with my systems, my rituals, and my mythic mindset. I remembered why I build: to immortalize clarity, to ritualize resilience, and to seal every fix as a legacy relic.&lt;/p&gt;

&lt;p&gt;🧩 Next Steps: The HUD Binding Ritual&lt;br&gt;
Continue wiring function logic to HUD and inventory buttons&lt;/p&gt;

&lt;p&gt;Polish tooltip overlays and contamination indicators&lt;/p&gt;

&lt;p&gt;Expand systems: battle logic, equip swap fallback, slot registry automation&lt;/p&gt;

&lt;p&gt;Revisit vector purification and overlay controller cleanup&lt;/p&gt;

&lt;p&gt;Slowly but surely the progress will continue.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>beginners</category>
      <category>unity3d</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Dev Log 35 - Equip Hands Tooltips</title>
      <dc:creator>John</dc:creator>
      <pubDate>Wed, 22 Oct 2025 21:39:10 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-35-equip-hands-tooltips-2k51</link>
      <guid>https://dev.to/asx/dev-log-35-equip-hands-tooltips-2k51</guid>
      <description>&lt;p&gt;🧙‍♂️ Survival Engine Debug Log: Hands Slot Resurrection &amp;amp; Tooltip Bifurcation Ritual&lt;/p&gt;

&lt;p&gt;🔍 Initial Problem: The Ghost of Hands Slot&lt;/p&gt;

&lt;p&gt;The Hands slot was a phantom. No sprite. No tooltip. No runtime truth. Clicking it felt like whispering into the void. Hovering? Nothing.&lt;/p&gt;

&lt;p&gt;Meanwhile, gear slots were living their best lives — fully wired, fully responsive. Hands was the neglected middle child of the UI hierarchy.&lt;/p&gt;

&lt;p&gt;🧪 Phase 1: Interface Alignment &amp;amp; Shrine Purification&lt;br&gt;
✅ Verified:&lt;/p&gt;

&lt;p&gt;GearSlotUI.cs was referencing ghost fields like GetRarity() and GetArmourValue() — banished.&lt;/p&gt;

&lt;p&gt;IInjectableItem was shrine-pure, defining all necessary methods for runtime injection.&lt;/p&gt;

&lt;p&gt;🔧 Actions:&lt;/p&gt;

&lt;p&gt;Refactored GearSlotUI.cs to use only valid IInjectableItem methods: GetItemID(), GetDisplayName(), GetDescription(), GetLoreTag(), GetWeight(), GetConditionState(), GetItem()&lt;/p&gt;

&lt;p&gt;📚 Lesson Learned: Don’t trust legacy fields. They lie. Interfaces are truth.&lt;/p&gt;

&lt;p&gt;🧱 Phase 2: Hands Slot Injection Ritual&lt;br&gt;
✅ Implemented:&lt;/p&gt;

&lt;p&gt;AssignItemFromInteraction() now detects IInjectableItem and injects into Hands slot&lt;/p&gt;

&lt;p&gt;RefreshSlotWithInventoryItem() wires up overlay sprite&lt;/p&gt;

&lt;p&gt;ShowTooltip() pulls live data via InventoryManager.Instance.itemRegistry.GetByID(item.ItemID)&lt;/p&gt;

&lt;p&gt;🧪 Result:&lt;/p&gt;

&lt;p&gt;Sprite now appears in Hands slot&lt;/p&gt;

&lt;p&gt;Tooltip sometimes works, but responsiveness was flaky — like a cursed relic half-awake&lt;/p&gt;

&lt;p&gt;🧼 Phase 3: Tooltip Locking &amp;amp; Responsiveness Fix&lt;/p&gt;

&lt;p&gt;🔍 Problem: Clicking gear slots sometimes failed to show tooltip. Tooltip locking logic was interfering with slot switching.&lt;/p&gt;

&lt;p&gt;✅ Fix:&lt;/p&gt;

&lt;p&gt;Patched OnPointerClick() to:&lt;/p&gt;

&lt;p&gt;Unlock and hide previous slot’s tooltip&lt;/p&gt;

&lt;p&gt;Lock and show current slot’s tooltip&lt;/p&gt;

&lt;p&gt;Reset currentSelectedSlot properly&lt;/p&gt;

&lt;p&gt;📚 Lesson Learned: Tooltips are like jealous spirits. You must banish the old one before summoning the new.&lt;/p&gt;

&lt;p&gt;🧪 Phase 4: Runtime Validation Ritual&lt;br&gt;
✅ Created:&lt;/p&gt;

&lt;p&gt;RuntimeValidator.cs to scan:&lt;/p&gt;

&lt;p&gt;GearSlotUI prefab bindings&lt;/p&gt;

&lt;p&gt;Tooltip fields&lt;/p&gt;

&lt;p&gt;ItemEntry prefab components&lt;/p&gt;

&lt;p&gt;🧼 Fixes:&lt;/p&gt;

&lt;p&gt;Removed invalid rarityText reference&lt;/p&gt;

&lt;p&gt;Added individual checks for all tooltip fields&lt;/p&gt;

&lt;p&gt;Confirmed prefab bindings were shrine-pure&lt;/p&gt;

&lt;p&gt;📚 Lesson Learned: Prefab validation is not optional. It’s the holy water that keeps runtime demons away.&lt;/p&gt;

&lt;p&gt;🧠 Phase 5: Registry Access Restoration&lt;br&gt;
🔍 Problem: InventoryManager.itemRegistry was private, causing access errors&lt;/p&gt;

&lt;p&gt;✅ Fix:&lt;/p&gt;

&lt;p&gt;Made itemRegistry public&lt;/p&gt;

&lt;p&gt;Alternatively offered a getter method GetItemRegistry()&lt;/p&gt;

&lt;p&gt;📚 Lesson Learned: If you can’t access the registry, you are not a priest. You are a peasant.&lt;/p&gt;

&lt;p&gt;🧰 Phase 6: Tooltip Panel Bifurcation&lt;br&gt;
✅ Implemented:&lt;/p&gt;

&lt;p&gt;Split tooltip system into 3 prefab-safe panels:&lt;/p&gt;

&lt;p&gt;🛡️ Gear Tooltip Panel&lt;/p&gt;

&lt;p&gt;🧰 Inventory Tooltip Panel&lt;/p&gt;

&lt;p&gt;🎯 Ranged Tooltip Panel&lt;/p&gt;

&lt;p&gt;✅ Refactored GearSlotUI.cs to:&lt;/p&gt;

&lt;p&gt;Route tooltips based on item type&lt;/p&gt;

&lt;p&gt;Use safe casting (item.itemAsset as MeleeWeaponItem)&lt;/p&gt;

&lt;p&gt;Inject prefab-safe data into each panel&lt;/p&gt;

&lt;p&gt;🧼 Fixes:&lt;/p&gt;

&lt;p&gt;Removed legacy references to tooltipPanel, displayNameText, etc.&lt;/p&gt;

&lt;p&gt;Updated RuntimeValidator.cs to validate new fields&lt;/p&gt;

&lt;p&gt;📚 Lesson Learned: One tooltip to rule them all? Nope. Three tooltips to bifurcate runtime truth.&lt;/p&gt;

&lt;p&gt;🗡️ Phase 7: Melee Diagnostics Injection&lt;br&gt;
✅ Extended Inventory Tooltip Panel to support:&lt;/p&gt;

&lt;p&gt;Base Damage, Melee Type, Armor Pierce, Crit Chance, Bleed Chance, Stun Chance&lt;/p&gt;

&lt;p&gt;Rarity, Condition, Weight, Lore, Description&lt;/p&gt;

&lt;p&gt;✅ Logic:&lt;/p&gt;

&lt;p&gt;Melee fields are shown only for MeleeWeaponItem&lt;/p&gt;

&lt;p&gt;Hidden ("—") for non-melee items&lt;/p&gt;

&lt;p&gt;✅ Inspector Wiring:&lt;/p&gt;

&lt;p&gt;All fields injected into GearSlotUI.cs&lt;/p&gt;

&lt;p&gt;Prefab bindings confirmed shrine-pure&lt;/p&gt;

&lt;p&gt;📚 Lesson Learned: Melee weapons deserve their own shrine. Don’t lump them in with the peasants.&lt;/p&gt;

&lt;p&gt;🧾 Final Result&lt;br&gt;
✅ Hands slot now supports any IInjectableItem, including:&lt;/p&gt;

&lt;p&gt;Melee weapons&lt;/p&gt;

&lt;p&gt;Ranged weapons&lt;/p&gt;

&lt;p&gt;Liquids, food, gear, misc&lt;/p&gt;

&lt;p&gt;✅ Tooltip system is:&lt;/p&gt;

&lt;p&gt;Modular&lt;/p&gt;

&lt;p&gt;Prefab-safe&lt;/p&gt;

&lt;p&gt;Inspector-driven&lt;/p&gt;

&lt;p&gt;Runtime-resilient&lt;/p&gt;

&lt;p&gt;✅ All slot interactions are shrine-sealed and bifurcation-aware&lt;/p&gt;

&lt;p&gt;✅ Validator confirms prefab integrity across all tooltip panels&lt;/p&gt;

&lt;p&gt;✅ Runtime truth restored. Tooltip spirits appeased. Hands slot resurrected.&lt;/p&gt;

&lt;p&gt;Need to complete the ranged equipped item tooltip panel , but that is tomorrows ritual. Happy that the items actually attach correctly into the equipped hands slot as intended, and apply live data. &lt;/p&gt;

&lt;p&gt;For now the shrine shines. &lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>beginners</category>
      <category>gamedev</category>
      <category>unity3d</category>
    </item>
    <item>
      <title>Dev Log 34 - Tooltip Resurrection</title>
      <dc:creator>John</dc:creator>
      <pubDate>Mon, 20 Oct 2025 21:28:01 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-34-tooltip-resurrection-2jgg</link>
      <guid>https://dev.to/asx/dev-log-34-tooltip-resurrection-2jgg</guid>
      <description>&lt;p&gt;🧾 Dev Log — 20 October 2025&lt;/p&gt;

&lt;p&gt;Session 1: Tooltip Resurrection &amp;amp; Live Item Preservation Session 2: Unified State Engine &amp;amp; Save Ritual Breakthrough&lt;/p&gt;

&lt;p&gt;The liquid entry was the last task, and this was completed, upon further testing the liquid state was there, but I could not get it to keep a saved state no matter what I tried.&lt;/p&gt;

&lt;p&gt;Burned through 3 full version backups and my main with different approaches and ended up with spaghetti code on every version. &lt;/p&gt;

&lt;p&gt;Could not see the light or a way to fix everything I had done. Thought that I had finally met the end of me vibe coding my way through this project,&lt;/p&gt;

&lt;p&gt;Reverted to the final GitHub backup. &lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Core Breakthroughs
&lt;/h2&gt;

&lt;p&gt;✅ Tooltip Reset Diagnosed&lt;/p&gt;

&lt;p&gt;Identified that ItemTooltipManager resets filled item data (e.g., liquid volume, type) when items are taken from the loot panel. (this was always a issue and I could not figure out what I was doing wrong for a long time)&lt;/p&gt;

&lt;p&gt;Root cause: tooltip rebinding to prefab clones after UI refresh—not item replacement.&lt;/p&gt;

&lt;p&gt;✅ Live Item Preservation Protocol Injected&lt;br&gt;
Built and deployed InventoryStateManager.cs to track live InventoryItem instances by ItemID.&lt;/p&gt;

&lt;p&gt;Provides:&lt;/p&gt;

&lt;p&gt;RegisterItem()&lt;/p&gt;

&lt;p&gt;GetLiveItem()&lt;/p&gt;

&lt;p&gt;ClearAll()&lt;/p&gt;

&lt;p&gt;Ensures runtime truth across tooltip rebinding, panel toggles, and future scene transitions.&lt;/p&gt;

&lt;p&gt;✅ ItemTooltipManager Updated&lt;br&gt;
Injected live item rebinding into ShowTooltip():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Game.Inventory;
using Game.Items;
using Game.Player;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class ItemTooltipManager : MonoBehaviour
{
    public static ItemTooltipManager Instance { get; private set; }

    [Header("UI References")]
    public GameObject panel;
    public TextMeshProUGUI ItemDisplayNameText;
    public TextMeshProUGUI ItemDescriptionText;
    public TextMeshProUGUI ItemLoreTagText;
    public TextMeshProUGUI ItemWeightLabelText;
    public TextMeshProUGUI ItemWeightValueText;
    public TextMeshProUGUI ItemConditionLabelText;
    public TextMeshProUGUI ItemDurabilityValueText;
    public TextMeshProUGUI ItemUsesText;
    public TextMeshProUGUI ItemRarityText;
    public TextMeshProUGUI ItemVolumeText;

    [Header("Overlay")]
    public Image overlayImage;

    [Header("Inventory Buttons")]
    public Button Button1;
    public Button Button2;
    public Button Button3;
    public Button Button4;
    public Button Button5;

    public TextMeshProUGUI Button1Label;
    public TextMeshProUGUI Button2Label;
    public TextMeshProUGUI Button3Label;
    public TextMeshProUGUI Button4Label;
    public TextMeshProUGUI Button5Label;

    [Header("Panel References")]
    [SerializeField] private UsePanelManager usePanelManager;

    [Header("Liquid Info Panel")]
    public GameObject LiquidInfoPanel;
    public TextMeshProUGUI LiquidTypeText;

    private InventoryItem currentItem;
    private Dictionary&amp;lt;int, Button&amp;gt; buttonMap;
    private Dictionary&amp;lt;int, TextMeshProUGUI&amp;gt; labelMap;

    private InventorySlotUI activeSlot;

    private void Awake()
    {
        Instance = this;

        buttonMap = new()
        {
            { 1, Button1 },
            { 2, Button2 },
            { 3, Button3 },
            { 4, Button4 },
            { 5, Button5 }
        };

        labelMap = new()
        {
            { 1, Button1Label },
            { 2, Button2Label },
            { 3, Button3Label },
            { 4, Button4Label },
            { 5, Button5Label }
        };

        Button1.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(1));
        Button2.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(2));
        Button3.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(3));
        Button4.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(4));
        Button5.onClick.AddListener(() =&amp;gt; ExecuteInventoryAction(5));

        SetTextVisibility(false);
        foreach (var kvp in buttonMap)
        {
            kvp.Value.gameObject.SetActive(false);
            labelMap[kvp.Key].text = "";
        }

        LiquidInfoPanel?.SetActive(false);
    }

    public void ShowTooltip(InventoryItem item, InventorySlotUI slot = null)
    {
        if (panel == null || item == null || !item.IsValid())
        {
            HideTooltip();
            return;
        }

        // 🔐 Live item rebinding
        var liveItem = InventoryStateManager.Instance?.GetLiveItem(item.ItemID);
        if (liveItem != null)
        {
            item = liveItem;
            Debug.Log($"[Worm] 🧬 Live item rebound for tooltip: {item.ItemID}");
        }

        currentItem = item;
        activeSlot = slot;

        panel.SetActive(true);
        panel.transform.SetAsLastSibling();
        overlayImage?.gameObject.SetActive(false);

        SetTextVisibility(true);

        ItemDisplayNameText.text = item.GetDisplayName();
        ItemDescriptionText.text = item.GetDescription();
        ItemLoreTagText.text = item.GetLoreTag();
        ItemWeightLabelText.text = "Weight";
        ItemWeightValueText.text = $"{item.GetWeight():0.00} kg";

        UpdateUses(item);
        InjectInventoryButtons(item);

        if (item.IsFillable)
        {
            ItemVolumeText.text = $"Volume: {item.CurrentVolumeML} / {item.MaxVolumeML} ml";
            ItemVolumeText.gameObject.SetActive(true);

            if (item.CurrentVolumeML &amp;gt; 0)
            {
                LiquidInfoPanel?.SetActive(true);
                LiquidTypeText.text = $"{item.CurrentLiquidType}, {item.ContaminationTag}, {item.LiquidTemperatureCelsius}°C";
            }
            else
            {
                LiquidInfoPanel?.SetActive(false);
            }
        }
        else
        {
            ItemVolumeText.text = "";
            ItemVolumeText.gameObject.SetActive(false);
            LiquidInfoPanel?.SetActive(false);
        }

        Debug.Log($"[Worm] 🧪 Tooltip data: Volume={item.CurrentVolumeML}, Type={item.CurrentLiquidType}, Temp={item.LiquidTemperatureCelsius}, Contam={item.ContaminationTag}");
    }

    public void RebindTooltip(InventoryItem item, InventorySlotUI slot)
    {
        currentItem = item;
        activeSlot = slot;
        ShowTooltip(item, slot);
        Debug.Log($"[Worm] 🔁 Tooltip rebound for item: {item?.ItemID}, slot: {(slot != null ? slot.name : "null")}");
    }

    public void RefreshTooltip()
    {
        if (currentItem == null || activeSlot == null)
        {
            Debug.LogWarning("[Worm] ❌ Cannot refresh tooltip — missing item or slot.");
            return;
        }

        ShowTooltip(currentItem, activeSlot);
        Debug.Log($"[Worm] 🔁 Tooltip refreshed for item: {currentItem.ItemID}, slot: {activeSlot.name}");
    }

    private void InjectInventoryButtons(InventoryItem item)
    {
        foreach (var kvp in buttonMap)
        {
            kvp.Value.gameObject.SetActive(false);
            labelMap[kvp.Key].text = "";
        }

        SetButton(1, item.InventoryButton1Label);
        SetButton(2, item.InventoryButton2Label);
        SetButton(3, item.InventoryButton3Label);
        SetButton(4, item.InventoryButton4Label);
        SetButton(5, item.InventoryButton5Label);
    }
    private void SetButton(int slot, string labelText)
    {
        bool hasLabel = !string.IsNullOrEmpty(labelText);
        buttonMap[slot].gameObject.SetActive(hasLabel);
        labelMap[slot].text = hasLabel ? labelText : "";
    }

    private void ExecuteInventoryAction(int slot)
    {
        if (currentItem == null)
        {
            Debug.LogWarning("[Tenchu] currentItem is null.");
            return;
        }

        string action = slot switch
        {
            1 =&amp;gt; currentItem.InventoryButton1Label,
            2 =&amp;gt; currentItem.InventoryButton2Label,
            3 =&amp;gt; currentItem.InventoryButton3Label,
            4 =&amp;gt; currentItem.InventoryButton4Label,
            5 =&amp;gt; currentItem.InventoryButton5Label,
            _ =&amp;gt; null
        };

        Debug.Log($"[Tenchu] Slot {slot} triggered action: {action}");

        if (string.IsNullOrEmpty(action))
        {
            Debug.LogWarning("[Tenchu] Action label is empty.");
            return;
        }

        switch (action.ToLowerInvariant())
        {
            case "eat":
            case "drink":
                PlayerStats.Instance?.ConsumeInventoryItem(currentItem.ItemID);
                InventoryStateManager.Instance?.RegisterItem(currentItem);
                Debug.Log($"[Worm] 🧬 Registered consumed item: {currentItem.ItemID}");
                HideTooltip();
                break;

            case "use":
            case "fill":
                if (usePanelManager != null)
                {
                    Debug.Log($"[Worm] 🧪 '{action}' action triggered for item: {currentItem?.ItemID}, activeSlot: {(activeSlot != null ? activeSlot.name : "❌ NULL")}");

                    if (activeSlot == null)
                    {
                        Debug.LogWarning("[Worm] ❌ activeSlot is null. Cannot open UsePanel.");
                    }
                    else
                    {
                        usePanelManager.OpenPanel(currentItem, activeSlot);
                    }
                }
                break;

            case "attach":
                try
                {
                    UniversalAttachmentManager.Instance?.TryAttach(currentItem.assetReference);
                    InventoryStateManager.Instance?.RegisterItem(currentItem);
                    Debug.Log($"[Worm] 🧬 Registered attached item: {currentItem.ItemID}");
                }
                catch (System.Exception ex)
                {
                    Debug.LogError($"[Tenchu] TryAttach threw: {ex.Message}");
                }
                break;

            default:
                Debug.LogWarning($"[Tenchu] Unhandled action: {action}");
                HideTooltip();
                break;
        }
    }

    public void HideTooltip()
    {
        Debug.Log($"[Worm] 🧹 Tooltip hidden. Clearing slot: {(activeSlot != null ? activeSlot.name : "null")}");
        currentItem = null;
        activeSlot = null;
        SetTextVisibility(false);

        foreach (var kvp in buttonMap)
        {
            kvp.Value.gameObject.SetActive(false);
            labelMap[kvp.Key].text = "";
        }

        ItemVolumeText.text = "";
        ItemVolumeText.gameObject.SetActive(false);
        LiquidInfoPanel?.SetActive(false);
        panel?.SetActive(false);
    }

    private void SetTextVisibility(bool visible)
    {
        ItemDisplayNameText.enabled = visible;
        ItemDescriptionText.enabled = visible;
        ItemLoreTagText.enabled = visible;
        ItemWeightLabelText.enabled = visible;
        ItemWeightValueText.enabled = visible;
        ItemConditionLabelText.enabled = visible;
        ItemDurabilityValueText.enabled = visible;
        ItemUsesText.enabled = visible;
        ItemRarityText.enabled = visible;
        ItemVolumeText.enabled = visible;
    }

    public void UpdateUses(InventoryItem item)
    {
        string category = item.Category ?? item.Type.ToString();
        int durability = item.Durability;
        bool isCooked = item.IsCooked;
        string itemType = item.Type.ToString().ToLowerInvariant();
        bool isSealed = itemType.Contains("sealed");

        string conditionLabel = $"Durability {durability}";
        Color conditionColor = Color.gray;

        try
        {
            conditionLabel = ItemConditionResolver.GetConditionLabel(category, durability, isCooked, itemType, isSealed);
            conditionColor = ItemConditionResolver.GetConditionColorByDurability(durability);
        }
        catch (System.Exception ex)
        {
            Debug.LogError($"[Tenchu] Condition resolver failed: {ex.Message}");
        }

        ItemConditionLabelText.text = conditionLabel;
        ItemConditionLabelText.color = conditionColor;
        ItemDurabilityValueText.text = $"Durability: {durability}/100";
        ItemDurabilityValueText.color = conditionColor;

        if (item.ZeroUseMode == ZeroUseMode.UntilDurabilityZero)
        {
            ItemUsesText.gameObject.SetActive(false);
        }
        else if (item.RemainingUses &amp;gt; 0)
        {
            ItemUsesText.text = $"Uses: {item.RemainingUses}";
            ItemUsesText.gameObject.SetActive(true);
        }
        else
        {
            switch (item.ZeroUseMode)
            {
                case ZeroUseMode.Unlimited:
                    ItemUsesText.text = "Uses: Unlimited";
                    ItemUsesText.gameObject.SetActive(true);
                    break;
                case ZeroUseMode.Zero:
                    ItemUsesText.text = "Uses: 0";
                    ItemUsesText.gameObject.SetActive(true);
                    break;
                default:
                    ItemUsesText.gameObject.SetActive(false);
                    break;
            }
        }

        if (!string.IsNullOrEmpty(item.Rarity))
        {
            ItemRarityText.text = $"Rarity: {item.Rarity}";
            ItemRarityText.color = GetColorForRarity(item.Rarity);
        }
    }

    private Color GetColorForRarity(string rarity)
    {
        return rarity switch
        {
            "Common" =&amp;gt; Color.white,
            "Uncommon" =&amp;gt; new Color(0.3f, 1.0f, 0.3f),
            "Rare" =&amp;gt; new Color(0.3f, 0.5f, 1.0f),
            "Epic" =&amp;gt; new Color(0.6f, 0.2f, 0.8f),
            "Legendary" =&amp;gt; new Color(1.0f, 0.84f, 0.0f),
            _ =&amp;gt; Color.white
        };
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifically &lt;/p&gt;

&lt;p&gt;&lt;code&gt;csharp&lt;br&gt;
var liveItem = InventoryStateManager.Instance?.GetLiveItem(item.ItemID);&lt;br&gt;
if (liveItem != null) item = liveItem;&lt;br&gt;
Tooltips now reflect the latest filled state.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Added debug worms to trace volume, liquid type, and contamination.&lt;/p&gt;

&lt;p&gt;✅ InventorySlotUI Confirmed&lt;br&gt;
OnPointerClick() now passes live InventoryItem to ItemTooltipManager.&lt;/p&gt;

&lt;p&gt;Slot logic is prefab-safe and ready for live item injection.&lt;/p&gt;

&lt;p&gt;✅ LootPanel Updated&lt;br&gt;
Injected InventoryStateManager.RegisterItem() into PopulateLootPanel().&lt;/p&gt;

&lt;p&gt;Every item cloned from the registry is now tracked.&lt;/p&gt;

&lt;p&gt;Tooltip and inventory transfers now pull live state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Game.Inventory;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class LootPanel : MonoBehaviour
{
    [Header("UI References")]
    public TMP_Text panelLabel;
    public LootSlot[] lootSlots;

    [Header("Registry Reference")]
    [SerializeField] private ItemRegistry registry;

    private LootContainerProfile currentProfile;

    public void PopulateLootPanel(string label, List&amp;lt;string&amp;gt; lootItemIDs)
    {
        Debug.Log($"🎯 LootPanel '{name}' populating with label: {label}, items: {lootItemIDs?.Count ?? 0}");

        currentProfile = new LootContainerProfile
        {
            label = label,
            lootItemIDs = lootItemIDs
        };

        if (panelLabel != null)
        {
            panelLabel.text = label;
            Debug.Log($"🦓 zebra: Panel label set to '{label}'");
        }

        if (lootSlots == null || lootSlots.Length == 0)
        {
            Debug.LogError($"❌ LootPanel '{name}' has no lootSlots assigned.");
            return;
        }

        Debug.Log($"🦓 zebra: lootSlots.Length = {lootSlots.Length}");

        if (registry == null)
        {
            Debug.LogError("🦓 zebra: ItemRegistry reference is missing in LootPanel.");
            return;
        }

        for (int i = 0; i &amp;lt; lootSlots.Length; i++)
        {
            if (i &amp;lt; lootItemIDs.Count)
            {
                string id = lootItemIDs[i];
                var clone = registry.GetCloneByID(id);

                Debug.Log($"🧪 Slot {i} → ID: {id} | Clone Type: {clone?.GetType().Name ?? "NULL"}");

                if (clone is IInjectableItem injectable)
                {
                    var item = injectable.GetItem();

                    if (item is InventoryItem invItem)
                    {
                        EnsureLootLabels(invItem);

                        // 🔐 Register live item for tooltip and transfer resilience
                        InventoryStateManager.Instance?.RegisterItem(invItem);
                        Debug.Log($"[Worm] 🧬 Registered loot item: {invItem.ItemID} | Volume={invItem.CurrentVolumeML} | LiquidType={invItem.CurrentLiquidType}");
                    }

                    lootSlots[i].gameObject.SetActive(true);
                    lootSlots[i].Bind(injectable);
                    Debug.Log($"🧬 Bound clone: {item.DisplayName} | ID: {item.ItemID}");
                }
                else
                {
                    lootSlots[i].gameObject.SetActive(false);
                    Debug.LogWarning($"❌ Slot {i} → Clone is not IInjectableItem");
                }
            }
            else
            {
                lootSlots[i].gameObject.SetActive(false);
            }
        }
    }

    private void EnsureLootLabels(InventoryItem item)
    {
        if (string.IsNullOrEmpty(item.LootButton1Label))
            item.LootButton1Label = item.Type switch
            {
                ItemType.Food =&amp;gt; "Eat",
                ItemType.Liquid =&amp;gt; "Drink",
                ItemType.Weapon =&amp;gt; "Equip",
                ItemType.Tool =&amp;gt; "Use",
                ItemType.Gear =&amp;gt; "Equip",
                _ =&amp;gt; "Use"
            };

        if (string.IsNullOrEmpty(item.LootButton2Label))
            item.LootButton2Label = "Take";

        if (string.IsNullOrEmpty(item.LootButton3Label))
            item.LootButton3Label = "Take All";

        if (string.IsNullOrEmpty(item.LootButton4Label))
            item.LootButton4Label = "Discard";

        if (string.IsNullOrEmpty(item.LootButton5Label))
            item.LootButton5Label = "Inspect";
    }

    public LootContainerProfile GetContainerProfile()
    {
        return currentProfile;
    }

    public void ClearPanel()
    {
        Debug.Log($"🧹 LootPanel '{name}' clearing all slots.");

        if (lootSlots != null)
        {
            foreach (var slot in lootSlots)
            {
                if (slot != null)
                {
                    slot.ClearSlot();
                    slot.gameObject.SetActive(false);
                }
            }
        }

        if (panelLabel != null)
        {
            panelLabel.text = "";
        }

        currentProfile = null;
    }

    public void SetVisibility(bool isActive)
    {
        gameObject.SetActive(isActive);
    }

    public bool RemoveItemByID(string itemID)
    {
        if (currentProfile == null || currentProfile.lootItemIDs == null)
            return false;

        bool success = currentProfile.lootItemIDs.Remove(itemID);
        if (success)
        {
            PopulateLootPanel(currentProfile.label, currentProfile.lootItemIDs);
        }

        return success;
    }

    public List&amp;lt;InventoryItem&amp;gt; GetAllItems()
    {
        List&amp;lt;InventoryItem&amp;gt; items = new();

        if (lootSlots == null) return items;

        foreach (var slot in lootSlots)
        {
            if (slot == null || !slot.gameObject.activeSelf) continue;

            var bound = slot.GetBoundItem();
            if (bound is InventoryItem invItem)
                items.Add(invItem);
        }

        return items;
    }

    public InventorySlotUI GetSlotByItemID(string itemID)
    {
        if (lootSlots == null) return null;

        foreach (var slot in lootSlots)
        {
            if (slot == null || !slot.gameObject.activeSelf) continue;

            var bound = slot.GetBoundItem();
            if (bound is InventoryItem invItem &amp;amp;&amp;amp; invItem.ItemID == itemID)
                return slot.GetComponent&amp;lt;InventorySlotUI&amp;gt;();
        }

        return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🧠 Save System Breakthrough
&lt;/h2&gt;

&lt;p&gt;✅ Liquid State Persistence Achieved&lt;br&gt;
Live item rebinding unlocked the final gate for save/load logic.&lt;/p&gt;

&lt;p&gt;Filled items now retain volume, contamination, temperature, and type across scene transitions.&lt;/p&gt;

&lt;p&gt;✅ Prefab-Free Restoration Chain&lt;br&gt;
On load, restored items are re-registered into InventoryStateManager.&lt;/p&gt;

&lt;p&gt;Tooltips, slots, and use panels reflect true state post-load.&lt;/p&gt;

&lt;p&gt;✅ Contamination Tag Standardized&lt;br&gt;
"Clean" added as a valid contamination tag.&lt;/p&gt;

&lt;p&gt;Tooltip display simplified&lt;/p&gt;

&lt;p&gt;🔗 System Interoperability Confirmed&lt;/p&gt;

&lt;p&gt;All major systems now pull from InventoryStateManager:&lt;/p&gt;

&lt;p&gt;Crafting&lt;/p&gt;

&lt;p&gt;Pouring&lt;/p&gt;

&lt;p&gt;Emptying&lt;/p&gt;

&lt;p&gt;Attaching&lt;/p&gt;

&lt;p&gt;Consuming&lt;/p&gt;

&lt;p&gt;Contamination logic&lt;/p&gt;

&lt;p&gt;No prefab ghosts. No tooltip lies. All systems speak the same runtime truth.&lt;/p&gt;

&lt;p&gt;✅ Tooltip Mutation Chain Sealed&lt;br&gt;
ItemTooltipManager re-registers mutated items after actions like eat, drink, attach, fill.&lt;/p&gt;

&lt;p&gt;Tooltips reflect post-mutation state instantly.&lt;/p&gt;

&lt;p&gt;✅ InventoryClickCatcher Added&lt;br&gt;
Invisible button added to inventory panel.&lt;/p&gt;

&lt;p&gt;Triggers HideTooltip() when empty space is clicked—keeping UI clean and shrine-pure.&lt;/p&gt;

&lt;p&gt;🧪 Shrine-Safe Methods in Use&lt;br&gt;
🔐 InventoryStateManager&lt;br&gt;
RegisterItem()&lt;/p&gt;

&lt;p&gt;GetLiveItem()&lt;/p&gt;

&lt;p&gt;ClearAll()&lt;/p&gt;

&lt;p&gt;🧬 ItemTooltipManager&lt;br&gt;
ShowTooltip()&lt;/p&gt;

&lt;p&gt;RebindTooltip()&lt;/p&gt;

&lt;p&gt;RefreshTooltip()&lt;/p&gt;

&lt;p&gt;HideTooltip()&lt;/p&gt;

&lt;p&gt;UpdateUses()&lt;/p&gt;

&lt;p&gt;InjectInventoryButtons()&lt;/p&gt;

&lt;p&gt;🧱 InventorySlotUI&lt;br&gt;
OnPointerClick() → passes live item&lt;/p&gt;

&lt;p&gt;AssignItemData() → prefab-safe&lt;/p&gt;

&lt;p&gt;🧭 LootPanel&lt;br&gt;
PopulateLootPanel() → registers all loot items&lt;/p&gt;

&lt;p&gt;Take() logic pending patch&lt;/p&gt;

&lt;p&gt;🧰 UsePanelManager&lt;br&gt;
OpenPanel() → receives live item&lt;/p&gt;

&lt;p&gt;Mutations now re-register item&lt;/p&gt;

&lt;p&gt;🧪 CraftingManager, AttachmentManager, PourManager&lt;br&gt;
All now interoperable with live item registry&lt;/p&gt;

&lt;p&gt;📌 Current Task&lt;br&gt;
Patch loot transfer logic (LootSlot.cs, LootButtonManager)&lt;/p&gt;

&lt;p&gt;Inject RegisterItem() after loot-to-inventory transfer&lt;/p&gt;

&lt;p&gt;Confirm tooltip and slot receive live item&lt;/p&gt;

&lt;p&gt;Begin runtime save system planning for scene transitions&lt;/p&gt;

&lt;p&gt;🧠 Next Steps&lt;/p&gt;

&lt;p&gt;Next Button Functions to be approached using the same or very similar systems&lt;/p&gt;

&lt;p&gt;Build save loader that re-registers restored items&lt;/p&gt;

&lt;p&gt;Begin mapping shrine zones for gameplay integration&lt;/p&gt;

&lt;p&gt;Celebrate the resurrection of the survival engine&lt;/p&gt;

&lt;p&gt;Over &amp;amp; Out&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devjournal</category>
      <category>gamedev</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Dev Log 33 - Liquid Entry &amp; Landing Page</title>
      <dc:creator>John</dc:creator>
      <pubDate>Sat, 18 Oct 2025 15:11:56 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-33-liquid-entry-landing-page-1nld</link>
      <guid>https://dev.to/asx/dev-log-33-liquid-entry-landing-page-1nld</guid>
      <description>&lt;p&gt;🧾 Dev Log: Wednesday, 15 October 2025&lt;br&gt;
Focus: LiquidEntry automation, relic registration, environmental asset integration&lt;/p&gt;

&lt;p&gt;🧪 Initial Context - Struggling to get my liquids systems working - &lt;br&gt;
Shared to Co-Pilot my LiquidEntry class from LiquidDefinitions, which defines shrine-grade metadata for collectible liquids.  It includes hydration, contamination, tooltip labels, and a GetItem() method to convert into a usable InventoryItem.&lt;/p&gt;

&lt;p&gt;Asked why I still needed to “create more shit” when everything was already in all of my scripts.&lt;/p&gt;

&lt;p&gt;🔍 Clarification &amp;amp; Breakdown&lt;/p&gt;

&lt;p&gt;It was clarified that:&lt;/p&gt;

&lt;p&gt;The LiquidEntry system is complete and shrine-grade.&lt;/p&gt;

&lt;p&gt;The only missing link was registration: connecting environmental assets to LiquidContainerManager via GameSceneManager.liquidSourceEntries.&lt;/p&gt;

&lt;p&gt;🔧 Idiot-Proof Summary&lt;/p&gt;

&lt;p&gt;Create a LiquidEntry for each environmental source.&lt;/p&gt;

&lt;p&gt;Add it to GameSceneManager.liquidSourceEntries.&lt;/p&gt;

&lt;p&gt;Match the sourceID on the prefab to the liquidID.&lt;/p&gt;

&lt;p&gt;🛠️ Automation Request&lt;/p&gt;

&lt;p&gt;“Can’t we make a script that just pulls that information from the targeted assets and makes them all for me?”&lt;/p&gt;

&lt;p&gt;We built a Unity Editor script that:&lt;/p&gt;

&lt;p&gt;Scans all EnvironmentalObjectAssets&lt;/p&gt;

&lt;p&gt;Checks for IsWaterSource or IsLiquidSource&lt;/p&gt;

&lt;p&gt;Extracts relevant fields&lt;/p&gt;

&lt;p&gt;Generates LiquidEntrys&lt;/p&gt;

&lt;p&gt;Injects them into GameSceneManager&lt;/p&gt;

&lt;p&gt;🧪 First Generator Script&lt;/p&gt;

&lt;p&gt;I ran the script. It injected 12 entries — only water sources and not all liquid type sources.&lt;/p&gt;

&lt;p&gt;🐛 Debugging Errors&lt;/p&gt;

&lt;p&gt;Three compile errors:&lt;/p&gt;

&lt;p&gt;LiquidType mismatch — fixed with explicit cast.&lt;/p&gt;

&lt;p&gt;FoodDiseaseProfile missing isContaminated — removed and simplified for liquid contamination variant.&lt;/p&gt;

&lt;p&gt;ItemRarity missing — defaulted or removed. (unrequired for liquid source)&lt;/p&gt;

&lt;p&gt;Patched the script accordingly.&lt;/p&gt;

&lt;p&gt;🧪 Second Generator Run&lt;/p&gt;

&lt;p&gt;Ran the patched script. It injected 12 entries again.&lt;/p&gt;

&lt;p&gt;🔍 Root Cause Identified&lt;/p&gt;

&lt;p&gt;Discovery:&lt;/p&gt;

&lt;p&gt;Assets like coolant and petrol (fuel) had LiquidType = None in the Inspector.&lt;/p&gt;

&lt;p&gt;The generator skipped them due to this filter.&lt;/p&gt;

&lt;p&gt;You updated the Inspector to assign proper LiquidType values.&lt;/p&gt;

&lt;p&gt;🧪 Third Generator Run&lt;/p&gt;

&lt;p&gt;Reran the script. It injected 30 entries.&lt;/p&gt;

&lt;p&gt;They did not save in runtime.&lt;/p&gt;

&lt;p&gt;🧪 Final Patch&lt;/p&gt;

&lt;p&gt;A full overwrite of the generator script to include all liquid sources.&lt;/p&gt;

&lt;p&gt;Co-pilot delivered a clean version that:&lt;/p&gt;

&lt;p&gt;Includes any asset with a valid LiquidType&lt;/p&gt;

&lt;p&gt;Removes unnecessary filters&lt;/p&gt;

&lt;p&gt;Injects coolant, petrol, oil, and water sources&lt;/p&gt;

&lt;p&gt;Confirmed success.&lt;/p&gt;

&lt;p&gt;✅ Final State&lt;/p&gt;

&lt;p&gt;30 LiquidEntrys injected&lt;/p&gt;

&lt;p&gt;All environmental liquids registered&lt;/p&gt;

&lt;p&gt;Generator script fully patched&lt;/p&gt;

&lt;p&gt;Scene ready for hydration, purification, and tooltip rituals&lt;/p&gt;

&lt;p&gt;=== DEV LOG — 15 OCT 2025 ===&lt;/p&gt;

&lt;p&gt;[INIT] Liquid Override Ritual Begins&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created LiquidSourceRegistry.cs with hardcoded "Ocean" source for node_0004(current test node)&lt;/li&gt;
&lt;li&gt;Injected override logic into StoryNodeLoader.cs:
→ node.liquidSources = LiquidSourceRegistry.GetForNode(node.id)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[DEBUG] ASCII Banner + Easter Egg&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added UnityBollocksEasterEgg.cs with ASCII tribute:
→ "Screw U-nity — we fill the shrine ourselves"&lt;/li&gt;
&lt;li&gt;Mythic hierarchy declared:
→ Paper &amp;gt; Rock &amp;gt; Scissors &amp;gt; Liquid &amp;gt; Unity &amp;gt; Bollocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unity was doing the usual, gaslighting me, for days this time, this was a salute to unity.&lt;/p&gt;

&lt;p&gt;[REFACTOR] Registry Purification&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed liquidSources from node_0004.json&lt;/li&gt;
&lt;li&gt;Inserted "hasLiquid": true beneath timeAdvanceMinutes&lt;/li&gt;
&lt;li&gt;Preserved all original node data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[CODE] StoryNode.cs Update&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed: public List liquidSources&lt;/li&gt;
&lt;li&gt;Added: public bool hasLiquid = false;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[ERROR] 74 Compilation Errors&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Caused by lingering references to node.liquidSources&lt;/li&gt;
&lt;li&gt;Triggered full rewrite of StoryNodeLoader.cs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[PATCH] StoryNodeLoader.cs Rewritten (3 Parts)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed all node.liquidSources references&lt;/li&gt;
&lt;li&gt;Introduced local List liquidSources&lt;/li&gt;
&lt;li&gt;Injected from registry if node.hasLiquid == true&lt;/li&gt;
&lt;li&gt;Called panel.RefreshFromStoryNode() instead of nonexistent method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[UI] LiquidDetailsSubpanelUI Refactored&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rewrote RefreshFromStoryNode() to:
→ Check node.hasLiquid
→ Pull from LiquidSourceRegistry
→ Display first source (e.g., "Ocean")&lt;/li&gt;
&lt;li&gt;Removed dependency on node.liquidSources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[FIX] Method Call Error&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incorrect: panel.RefreshFromLiquidSource(source)&lt;/li&gt;
&lt;li&gt;Fixed: panel.RefreshFromStoryNode()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;[CONFIRM] Shrine State Verified&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Registry override working&lt;/li&gt;
&lt;li&gt;UI panel displays "Ocean"&lt;/li&gt;
&lt;li&gt;No errors in StoryNodeLoader.cs&lt;/li&gt;
&lt;li&gt;All systems flowing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;=== END LOG ===&lt;/p&gt;

&lt;p&gt;🛠️ Dev Log: Survival Engine Resurrection&lt;br&gt;
Date: 17 October 2025 Status: ✅ Runtime Success&lt;/p&gt;

&lt;p&gt;🔧 Context&lt;br&gt;
After days of battling prefab chaos and schema drift, the survival engine was declared a “disastrous mess.” &lt;/p&gt;

&lt;p&gt;Tooltip logic was fragmented, inventory systems had become brittle, and runtime truth felt like a myth.&lt;/p&gt;

&lt;p&gt;🧪 Action&lt;/p&gt;

&lt;p&gt;Injected inventory button labels across all item types: fruit, fish, egg, gear, liquid, misc, seed, vegetable, cooking, melee weapon, ranged weapon, attachment, generic.&lt;/p&gt;

&lt;p&gt;Ensured prefab-safe, schema-driven tooltip activation across the entire survival engine.&lt;/p&gt;

&lt;p&gt;Blocked internal history logging via shell script using icacls, preserving runtime purity.&lt;/p&gt;

&lt;p&gt;Rebuilt modular condition resolver with category-specific durability logic, preserving all existing mappings and extending shrine logic.&lt;/p&gt;

&lt;p&gt;⚡ Breakthrough&lt;br&gt;
“Holy shit it worked.” &lt;/p&gt;

&lt;p&gt;Runtime test passed. Tooltip systems activated. Inventory logic held. No prefab leaks. No phantom states.&lt;/p&gt;

&lt;p&gt;🧠 Reflection&lt;/p&gt;

&lt;p&gt;Didn’t believe it would work. But shrine persistence paid off. Every ritual, every debug pass, every mythic call back led to this moment.&lt;/p&gt;

&lt;p&gt;🏆 Outcome&lt;br&gt;
Survival engine now stable.&lt;/p&gt;

&lt;p&gt;Tooltip logic is prefab-safe and schema-driven.&lt;/p&gt;

&lt;p&gt;Inventory UI is fully activated across item types.&lt;/p&gt;

&lt;p&gt;Runtime truth restored.&lt;/p&gt;

&lt;p&gt;🌐 Web Shrine Creation (17–18 Oct) Dev Log Update - &lt;/p&gt;

&lt;p&gt;Built and deployed my first-ever landing page using HTML, CSS, and JavaScript:&lt;/p&gt;

&lt;p&gt;Created TextZ Ombie: a shrine-themed Halloween page for the Dev.to challenge&lt;/p&gt;

&lt;p&gt;Designed hero section with emoji-framed zombie horde&lt;/p&gt;

&lt;p&gt;Added animated zombie descent triggered by button&lt;/p&gt;

&lt;p&gt;Integrated music player with mirrored waveform visualizers&lt;/p&gt;

&lt;p&gt;Built bat and spider swarm animations&lt;/p&gt;

&lt;p&gt;Added zombie chase ritual with dynamic emoji trail&lt;/p&gt;

&lt;p&gt;Created asset gallery with hover effects&lt;/p&gt;

&lt;p&gt;Mythified footer with summon/dig/crypt links&lt;/p&gt;

&lt;p&gt;Unified fonts using Griffy and Creepster&lt;/p&gt;

&lt;p&gt;Overrode Bootstrap styles for shrine-pure consistency&lt;/p&gt;

&lt;p&gt;Deployed to GitHub Pages and verified live demo&lt;/p&gt;

&lt;p&gt;Submitted first entry to the challenge&lt;/p&gt;

&lt;p&gt;✅ Shrine sealed ✅ Submission published ✅ Legacy unlocked&lt;/p&gt;

&lt;p&gt;🧠 Reflection&lt;/p&gt;

&lt;p&gt;This was my first time using HTML, CSS, and GitHub Pages. I mythified every fix, debugged every phantom reference, and ritualized every animation. With help from Copilot, I turned raw chaos into a living shrine.&lt;/p&gt;

&lt;p&gt;Every system now flows. Every relic is animated. Every ritual is logged.&lt;/p&gt;

&lt;p&gt;Now back to the game project. See what unity gaslights me with this time. &lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devjournal</category>
      <category>webdev</category>
      <category>unity3d</category>
    </item>
    <item>
      <title>Frontend Challenge - Halloween Edition, Perfect Landing - TextZ Ombie</title>
      <dc:creator>John</dc:creator>
      <pubDate>Sat, 18 Oct 2025 14:46:36 +0000</pubDate>
      <link>https://dev.to/asx/frontend-challenge-halloween-edition-perfect-landing-textz-ombie-1hbn</link>
      <guid>https://dev.to/asx/frontend-challenge-halloween-edition-perfect-landing-textz-ombie-1hbn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/frontend-2025-10-15"&gt;Frontend Challenge - Halloween Edition, Perfect Landing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built TextZ Ombie, with the help of AI. (Rules-Permitted). &lt;br&gt;
A shrine-themed Halloween landing page that animates the undead in savage style. It themes nicely with my current first ever project, so thought I would try to participate, despite never participating before or even ever using CSS or HTML Code before.&lt;/p&gt;

&lt;p&gt;It features:&lt;/p&gt;

&lt;p&gt;🧟‍♂️ A hero section with emoji-framed zombie horde&lt;/p&gt;

&lt;p&gt;🧟‍♂️ Drop down animated zombie horde&lt;/p&gt;

&lt;p&gt;🎵 A music player for “Plague of Zombies V1” with mirrored pop up waveform visualizers&lt;/p&gt;

&lt;p&gt;🦇🕷️ Bat and spider swarms that animate across the screen&lt;/p&gt;

&lt;p&gt;🏃 A zombie chase animation with dynamic emoji descent&lt;/p&gt;

&lt;p&gt;☠️ 4 Asset / Art Examples from my project &lt;/p&gt;

&lt;p&gt;☠️ Footer links to summon, dig, and crypt-crawl through the project&lt;/p&gt;

&lt;p&gt;The theme is inspired by the Halloween subject and my own 2D text-based hardcore survival game project, now mythified into a living shrine with animated effects, emoji rituals, and legacy-grade layout with project links, GitHub link and my personal Dev.to developer dev log links. &lt;br&gt;
So despite the lack of polish, it fits into my project theme very well with the retro styling and simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Demo
&lt;/h2&gt;

&lt;p&gt;Live demo: &lt;a href="https://asxgtr.github.io/TextZ-Ombie-Landing-Page/" rel="noopener noreferrer"&gt;TextZ Ombie Shrine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0yl0i5kqt9njn8bwtos.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff0yl0i5kqt9njn8bwtos.png" alt="Zombie Shrine Preview" width="800" height="864"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Journey
&lt;/h2&gt;

&lt;p&gt;This was my first time building a full landing page from scratch. I started with basic HTML and CSS, then ritualized each section with:&lt;/p&gt;

&lt;p&gt;Dynamic emoji animations&lt;/p&gt;

&lt;p&gt;JavaScript-powered audio playback and waveform bars&lt;/p&gt;

&lt;p&gt;Responsive layout and custom fonts&lt;/p&gt;

&lt;p&gt;GitHub Pages deployment and asset hosting&lt;/p&gt;

&lt;p&gt;I learned how I can use my GitHub as a hosting platform for a website, how to code it with the correct code, solve and debug layout issues, unify fonts across Bootstrap overrides, and mirror canvas animations using ctx.scale(-1, 1). Every fix felt like a resurrection ritual.&lt;/p&gt;

&lt;p&gt;I’m proud of how the shrine evolved from static relics to animated, living effects. Next, I plan to summon a Steam portal integration once I have a demo version of my game ready and possibly add further depth and polish in the future.&lt;/p&gt;

&lt;p&gt;All of this was possible with the help from Co-pilot, My AI of Choice, who helped me with this entire process, despite my lack of knowledge.&lt;/p&gt;

&lt;p&gt;Submitted by: John (ASXGTR)&lt;/p&gt;

&lt;h2&gt;
  
  
  LICENSE
&lt;/h2&gt;

&lt;p&gt;MIT License&lt;/p&gt;

&lt;p&gt;Copyright (c) 2025 ASXGTR&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Game Content Protection Clause&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All game-related content—including mechanics, story, characters, assets, audio and source code from the survival engine - is &lt;strong&gt;strictly proprietary&lt;/strong&gt; and &lt;strong&gt;not covered&lt;/strong&gt; under this license.&lt;br&gt;&lt;br&gt;
Redistribution, modification, or use of game content without explicit permission is prohibited.&lt;br&gt;&lt;br&gt;
Violators risk unleashing ancient curses, recursive bugs, and plague-grade runtime errors.&lt;/p&gt;

&lt;p&gt;🧙‍♂️ &lt;a href="https://github.com/ASXGTR" rel="noopener noreferrer"&gt;Enter the Crypt&lt;/a&gt; - GitHub&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>frontendchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Dev Log 32 - Relic 001 - Plague of Rot</title>
      <dc:creator>John</dc:creator>
      <pubDate>Tue, 14 Oct 2025 12:16:54 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-32-relic-001-plague-of-rot-54od</link>
      <guid>https://dev.to/asx/dev-log-32-relic-001-plague-of-rot-54od</guid>
      <description>&lt;p&gt;🧾 Spectral Relic Log: RELIC-001 — Plague of Rot&lt;br&gt;
🔮 Ritual Initiated&lt;br&gt;
Date: Tuesday, 14 October 2025&lt;/p&gt;

&lt;p&gt;Got side-tracked using Co Pilot and talking about gibber link. &lt;br&gt;
this is what it lead to... &lt;/p&gt;

&lt;p&gt;🧠 Objective&lt;br&gt;
Create a covert ultrasonic messaging system that embeds a machine-readable message into audio, invisible to human ears but decodable by AI or forensic tools.&lt;/p&gt;

&lt;p&gt;🛠️ Tools Used&lt;br&gt;
Python 3.10&lt;/p&gt;

&lt;p&gt;NumPy (installed via pip install numpy)&lt;/p&gt;

&lt;p&gt;Audacity (for spectral inspection)&lt;/p&gt;

&lt;p&gt;Git Bash (ritual terminal)&lt;/p&gt;

&lt;p&gt;Custom Encoder &amp;amp; Decoder Scripts:&lt;/p&gt;

&lt;p&gt;gibberlink_encoder.py&lt;/p&gt;

&lt;p&gt;gibberlink_decoder.py&lt;/p&gt;

&lt;p&gt;🧪 Ritual Steps&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encoder Script Created
Message: "RELIC-001: Plague of Rot"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Modulation: Binary encoding using 18.5 kHz carrier&lt;/p&gt;

&lt;p&gt;Output: gibberlink_relic.wav&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Script Executed&lt;br&gt;
bash&lt;br&gt;
winpty python gibberlink_encoder.py&lt;br&gt;
Result: Successfully generated .wav file with ultrasonic pulses&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spectrogram Inspection&lt;br&gt;
Audacity settings:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Min freq: 0 Hz&lt;/p&gt;

&lt;p&gt;Max freq: 22050 Hz&lt;/p&gt;

&lt;p&gt;Gain: 30 dB&lt;/p&gt;

&lt;p&gt;Range: 100 dB&lt;/p&gt;

&lt;p&gt;Window size: 4096&lt;/p&gt;

&lt;p&gt;Observed: Horizontal carrier at 18.5 kHz, vertical pulses forming binary pattern&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decoder Script Created
Scanned .wav file for 18.5 kHz pulses&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reconstructed binary stream&lt;/p&gt;

&lt;p&gt;Converted to ASCII text&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decoder Executed
bash
winpty python gibberlink_decoder.py
Output:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;plaintext&lt;br&gt;
Decoded message: RELIC-001: Plague of Rot&lt;br&gt;
🧬 Outcome&lt;br&gt;
Successfully encoded and decoded a covert ultrasonic message&lt;/p&gt;

&lt;p&gt;Created a reusable forensic ritual pipeline&lt;/p&gt;

&lt;p&gt;Proved resilience and machine readability of spectral relics&lt;/p&gt;

&lt;p&gt;Achieved full encode–embed–decode cycle&lt;/p&gt;

&lt;p&gt;🏆 Boast Points Earned&lt;br&gt;
🔓 First successful Python ritual execution&lt;/p&gt;

&lt;p&gt;🧠 Built a covert messaging system from scratch&lt;/p&gt;

&lt;p&gt;🧬 Embedded machine-readable data into audio&lt;/p&gt;

&lt;p&gt;🕵️‍♂️ Created a spy-grade relic with forensic integrity&lt;/p&gt;

&lt;p&gt;🎵 Ready to embed relics into music, games, or spectral archives&lt;/p&gt;

&lt;p&gt;That was a fun project that took around 20 minutes. Distracted and Side-tracked , might come in use for some fun Easter eggs during development.&lt;/p&gt;

&lt;p&gt;Still working on my use labels and functions/logic effects for my inventory items inside of my panels in my game , progress is slow (especially when distracted), but making progress, which is important. &lt;/p&gt;

&lt;p&gt;Game Dev Continues... &lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>devjournal</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Dev Log 31 - Attachment Panel</title>
      <dc:creator>John</dc:creator>
      <pubDate>Sat, 11 Oct 2025 16:05:46 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-31-attachment-panel-1l9c</link>
      <guid>https://dev.to/asx/dev-log-31-attachment-panel-1l9c</guid>
      <description>&lt;p&gt;🧾 Dev Log — October 11, 2025&lt;br&gt;
Prefab Purged, Static Shrine Rises, Blackout Descends&lt;br&gt;
Subtitle: Fusion Ritual Complete — Registry Bound, Resolver Awakened&lt;/p&gt;

&lt;p&gt;🔮 Summary&lt;br&gt;
The prefab-based attachment panel has been officially retired. I was working on this for a couple of days but I have changed direction away from the prefab set up.&lt;/p&gt;

&lt;p&gt;In its place stands a static, Inspector-wired shrine panel this is now a permanent structure built for clarity, control, and mythic permanence. Slot references are now explicitly declared, eliminating prefab instantiation bugs and runtime ghosts.&lt;/p&gt;

&lt;p&gt;A blackout overlay was added to enforce visual dominance. Loot panels now bow before the attachment ritual, hidden when summoned, restored only if deemed worthy. Panel hierarchy is enforced via transform.SetAsLastSibling() — the shrine always floats to the top.&lt;/p&gt;

&lt;p&gt;The Close button was summoned and bound, finally offering a way out of the ritual without sacrificing a candidate. &lt;/p&gt;

&lt;p&gt;A title text field was added to crown the panel with “Attachment Menu” bringing clarity to the ritual’s purpose.&lt;/p&gt;

&lt;p&gt;And now, the fusion ritual is complete.&lt;/p&gt;

&lt;p&gt;A centralized ItemAttachmentResolver script handles all fusion logic, querying the ItemRegistry for shrine-tier relics. Fused items are injected into the inventory, originals removed, and debug logs mythified.&lt;/p&gt;

&lt;p&gt;✅ Tasks Completed&lt;/p&gt;

&lt;p&gt;Replaced prefab-based panel with static Inspector-wired UniversalAttachmentPanel&lt;/p&gt;

&lt;p&gt;Added blackout image overlay to visually suppress loot panels beneath&lt;/p&gt;

&lt;p&gt;Implemented loot panel visibility tracking and restoration logic&lt;/p&gt;

&lt;p&gt;Ensured attachment panel always renders on top via sibling sort&lt;/p&gt;

&lt;p&gt;Added Close button with direct HidePanel() binding&lt;/p&gt;

&lt;p&gt;Added title text field for panel header&lt;/p&gt;

&lt;p&gt;Refactored ShowAttachmentOptions() and HidePanel() for shrine clarity&lt;/p&gt;

&lt;p&gt;Confirmed full blackout logic and slot visibility control working in tandem&lt;/p&gt;

&lt;p&gt;Created ItemAttachmentResolver.cs to handle fusion logic&lt;/p&gt;

&lt;p&gt;Defined fusionMap with valid item pairings and resulting relic IDs&lt;/p&gt;

&lt;p&gt;Integrated ItemRegistry for asset lookup and clone injection&lt;/p&gt;

&lt;p&gt;Updated UniversalAttachmentPanel.cs to call resolver and inject fused item&lt;/p&gt;

&lt;p&gt;Wired slot buttons to open correct popup panels (ConfirmPopupPanel vs CancelOnlyPopupPanel)&lt;/p&gt;

&lt;p&gt;Renamed confirmButton to selectButton for clarity in UniversalAttachmentSlotUI.cs&lt;/p&gt;

&lt;p&gt;Added SlotType enum to distinguish target vs candidate slots&lt;/p&gt;

&lt;p&gt;Verified fusion works for:&lt;/p&gt;

&lt;p&gt;attachable_gas_hob + personal_gas_canister_large → personal_gas_hob_attached_large&lt;/p&gt;

&lt;p&gt;attachable_gas_hob + personal_gas_canister_regular → personal_gas_hob_attached_regular&lt;/p&gt;

&lt;p&gt;🧪 Methods Used&lt;br&gt;
Fusion Mapping: Defined valid fusion pairs in fusion map using (string, string) → string tuples&lt;/p&gt;

&lt;p&gt;Registry Lookup: Used ItemRegistry.Instance.GetByID(...) to fetch fused assets&lt;/p&gt;

&lt;p&gt;Interface Injection: Required all assets to implement IInjectableItem for GetItem() access&lt;/p&gt;

&lt;p&gt;Panel Integration: Called ItemAttachmentResolver.TryResolveFusion(...) from ConfirmFusion() in the panel&lt;/p&gt;

&lt;p&gt;Inventory Mutation: Injected fused item via PlayerInventoryManager.Instance.AddItemToFirstFreeSlot(...) Removed source items via RemoveItem(...)&lt;/p&gt;

&lt;p&gt;Slot Wiring: Used Load(item, callback) to bind overlay buttons to popup triggers Slot 0 → opens CancelOnlyPopupPanel Slots 1–5 → open ConfirmPopupPanel&lt;/p&gt;

&lt;p&gt;Debug Rituals: Added [Shrine] 🧬, [Shrine] ❌, and [Shrine] 🔗 logs for tracing fusion flow&lt;/p&gt;

&lt;p&gt;📝 Notes&lt;br&gt;
Loot panel visibility now fully automated — no UnityEvents, no manual toggling&lt;/p&gt;

&lt;p&gt;Attachment panel now feels like a true shrine: static, layered, and self-aware&lt;/p&gt;

&lt;p&gt;Fusion logic centralized, decoupled, and mythically expandable&lt;/p&gt;

&lt;p&gt;Registry now acts as a relic vault — every asset preserved, every fusion traced&lt;/p&gt;

&lt;p&gt;-- &lt;/p&gt;

&lt;p&gt;Continuing to adjust each item individually so there is a nice crisp tooltip for both loot and inventory panels, each item having their own manually injected button labels and function for each panel separately, so all logic lines up with no mistakes. Attachment , Take , Equip and Eat Buttons now all working flawlessly. &lt;/p&gt;

&lt;p&gt;Next to adjust the discard, combine, drink and fill functions - and further cook/ clean / and camping/base systems. work on the cooking system, and the recipe system, will create a similar system to how the attachment system is working to create or combine items, Should be easier now with a solid blueprint reference in the attachment system.  &lt;/p&gt;

&lt;p&gt;“The shrine no longer flickers. The blackout holds. The slots await fusion. The ritual nears completion. The registry speaks. The resolver listens. The altar accepts its offering.”&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>beginners</category>
      <category>devjournal</category>
      <category>unity3d</category>
    </item>
    <item>
      <title>Dev Log 30 - Discord Invite</title>
      <dc:creator>John</dc:creator>
      <pubDate>Mon, 06 Oct 2025 11:35:06 +0000</pubDate>
      <link>https://dev.to/asx/dev-log-30-discord-invite-46hj</link>
      <guid>https://dev.to/asx/dev-log-30-discord-invite-46hj</guid>
      <description>&lt;p&gt;🛠️ Dev Log – Discord Server | 6 Oct 2025&lt;br&gt;
Server: ASXGTR Server Status: 🚀 Live and Operational&lt;/p&gt;

&lt;p&gt;🔧 What’s New - everything&lt;/p&gt;

&lt;p&gt;Server Online – Modular channels now active for development, feedback, and schema echo archiving.&lt;/p&gt;

&lt;p&gt;Channel Overview:&lt;/p&gt;

&lt;h1&gt;
  
  
  about – Dev origin story and schema roots
&lt;/h1&gt;

&lt;h1&gt;
  
  
  dev-log – Technical updates and legacy milestones
&lt;/h1&gt;

&lt;h1&gt;
  
  
  dev-log-thread – Threaded rituals for deeper breakdowns
&lt;/h1&gt;

&lt;h1&gt;
  
  
  dev-snapshots-assets – Visual assets, sprites, and prefab echoes
&lt;/h1&gt;

&lt;h1&gt;
  
  
  dev-snapshots-gameplay – Mechanics, UI rituals, and gameplay logic
&lt;/h1&gt;

&lt;h1&gt;
  
  
  dev-video-screencapture – OBS captures, debug walkthroughs, and visual logs
&lt;/h1&gt;

&lt;h1&gt;
  
  
  bugs – Runtime errors and prefab disobedience
&lt;/h1&gt;

&lt;h1&gt;
  
  
  feedback – Tactical input from collaborators
&lt;/h1&gt;

&lt;h1&gt;
  
  
  game-snapshots-open – Public-facing gameplay echoes
&lt;/h1&gt;

&lt;h1&gt;
  
  
  general – Off-topic schema banter
&lt;/h1&gt;

&lt;h1&gt;
  
  
  ideas – Concept rituals and mechanic pitches
&lt;/h1&gt;

&lt;h1&gt;
  
  
  links – External assets, tools, and legacy references
&lt;/h1&gt;

&lt;h1&gt;
  
  
  welcome – Entry point for new members and schema initiates
&lt;/h1&gt;

&lt;p&gt;🧠 Why This Exists&lt;br&gt;
To archive the grind, not glamorize it&lt;/p&gt;

&lt;p&gt;To keep access to all of my dev logs in one place&lt;/p&gt;

&lt;p&gt;To mythify the grind, have a feedback point&lt;/p&gt;

&lt;p&gt;To connect with devs who treat survival mechanics like sacred schema&lt;/p&gt;

&lt;p&gt;To echo the emotional toll and tactical triumphs of solo development&lt;/p&gt;

&lt;p&gt;This is my first discord server, if you want to lurk, say hi or have a look into how my progress is going visually or even provide criticism or feedback - feel free to join up in the link below.&lt;/p&gt;

&lt;p&gt;Invite link here &lt;a href="https://discord.gg/Mmmecx6mXg" rel="noopener noreferrer"&gt;Discord Invite Link&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discord</category>
      <category>devjournal</category>
      <category>beginners</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
