<?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>Forem: John</title>
    <description>The latest articles on Forem by John (@asx).</description>
    <link>https://forem.com/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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3422762%2F59ef83cf-e027-46ce-a59f-b6b771901029.png</url>
      <title>Forem: John</title>
      <link>https://forem.com/asx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/asx"/>
    <language>en</language>
    <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://forem.com/asx/from-my-first-devto-challenge-to-building-my-own-website-2ph7</link>
      <guid>https://forem.com/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://forem.com/asx/dev-log-38-the-christmas-resurrection-kind-of--2gnm</link>
      <guid>https://forem.com/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="237"&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="893"&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://forem.com/asx/dev-log-37-consolidated-update-oo7</link>
      <guid>https://forem.com/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://forem.com/asx/dev-log-36-mythic-pause-4apk</link>
      <guid>https://forem.com/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://forem.com/asx/dev-log-35-equip-hands-tooltips-2k51</link>
      <guid>https://forem.com/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://forem.com/asx/dev-log-34-tooltip-resurrection-2jgg</link>
      <guid>https://forem.com/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://forem.com/asx/dev-log-33-liquid-entry-landing-page-1nld</link>
      <guid>https://forem.com/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://forem.com/asx/frontend-challenge-halloween-edition-perfect-landing-textz-ombie-1hbn</link>
      <guid>https://forem.com/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://forem.com/asx/dev-log-32-relic-001-plague-of-rot-54od</link>
      <guid>https://forem.com/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://forem.com/asx/dev-log-31-attachment-panel-1l9c</link>
      <guid>https://forem.com/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://forem.com/asx/dev-log-30-discord-invite-46hj</link>
      <guid>https://forem.com/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>
    <item>
      <title>Dev Log 29 - Unity Security Update</title>
      <dc:creator>John</dc:creator>
      <pubDate>Sun, 05 Oct 2025 14:05:41 +0000</pubDate>
      <link>https://forem.com/asx/dev-log-29-unity-security-update-3fm2</link>
      <guid>https://forem.com/asx/dev-log-29-unity-security-update-3fm2</guid>
      <description>&lt;p&gt;🛠️ Dev Log — Sunday, 5 October 2025&lt;br&gt;
Unity Upgrade &amp;amp; Security Patch + Loot Panel Refactor&lt;/p&gt;

&lt;p&gt;🔧 Engine Upgrade &amp;amp; Vulnerability Purge&lt;br&gt;
Detected Unity version 6000.0.5f31 flagged with Security Alert in Unity Hub, regarding recent vulnerabilities found in unity builds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discussions.unity.com/t/unity-platform-protection-take-immediate-action-to-protect-your-games-and-apps/1688031" rel="noopener noreferrer"&gt;Unity Platform Protection Notice&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Installed Unity 6000.2.6f2 (Unity 6.2) with full WebGL + Windows support&lt;/p&gt;

&lt;p&gt;Rebound project manually via ProjectVersion.txt&lt;/p&gt;

&lt;p&gt;Launched project from root folder after Unity Hub failed to auto-switch&lt;/p&gt;

&lt;p&gt;Survived Unity’s reimport cycle: Library rebuild, shader sync, script recompile&lt;/p&gt;

&lt;p&gt;🧹 Deprecated Package Cleanup&lt;br&gt;
Removed Samsung Android SDK (mobile build no longer targeted)&lt;/p&gt;

&lt;p&gt;Purged JetBrains Rider Editor package (confirmed Visual Studio 2022 as active IDE)&lt;/p&gt;

&lt;p&gt;Cleared all deprecated package warnings via Package Manager&lt;/p&gt;

&lt;p&gt;🧪 Runtime Integrity Checks&lt;br&gt;
Project loaded successfully after extended reimport&lt;/p&gt;

&lt;p&gt;Validated:&lt;/p&gt;

&lt;p&gt;Prefab integrity&lt;/p&gt;

&lt;p&gt;Slot permanence&lt;/p&gt;

&lt;p&gt;Tooltip label obedience&lt;/p&gt;

&lt;p&gt;No [Obsolete] warnings or missing script bindings&lt;/p&gt;

&lt;p&gt;WebGL build path confirmed clean and supported&lt;/p&gt;

&lt;p&gt;🧠 IDE Hygiene&lt;br&gt;
IDE flow verified through Visual Studio 2022&lt;/p&gt;

&lt;p&gt;Removed unnecessary Rider hooks to declutter scripting environment&lt;/p&gt;

&lt;p&gt;IDE integration now matches actual workflow—no drift, no ghost packages&lt;/p&gt;

&lt;p&gt;📦 Loot Panel System Refactor&lt;br&gt;
✅ Key Milestones&lt;br&gt;
Refactored StoryNodeLoader to pass all containers to a single LootPanelManager&lt;/p&gt;

&lt;p&gt;Patched LootPanelManager.InitializePanels() to loop through containers and bind slots dynamically&lt;/p&gt;

&lt;p&gt;Validated prefab integrity across all panels—slot prefab fields assigned, tooltip logic obeys&lt;/p&gt;

&lt;p&gt;Confirmed runtime activation of panels 2 and 3 with correct slot counts and item data&lt;/p&gt;

&lt;p&gt;Stress-tested with 7 items across 3 containers (Beach, Box, Safe) no layout drift or prefab breach&lt;/p&gt;

&lt;p&gt;🧱 Relics Activated&lt;br&gt;
seaweed_raw, eel_raw&lt;/p&gt;

&lt;p&gt;RustyMeatCleaver, MeatTenderizer&lt;/p&gt;

&lt;p&gt;peach_raw, wooden_spatula, trout_grilled&lt;/p&gt;

&lt;p&gt;📜 Schema Notes&lt;br&gt;
Panels remain inactive unless containers exist and contain items&lt;/p&gt;

&lt;p&gt;Slot prefab reuse confirmed safe across multiple panels&lt;/p&gt;

&lt;p&gt;Tooltip manager obeys slot click ritual across all panels&lt;/p&gt;

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

&lt;p&gt;Fix the equipped hands items, to actually show in the correct slot, these are equipping and blocking secondary equip as intended but no sprite is visible when equipped , no tooltip showing despite being attached for this slot only.&lt;/p&gt;

&lt;p&gt;Validate tab switching and active panel sync - sync into inventory working correctly.&lt;/p&gt;

&lt;p&gt;Audit tooltip manager behaviour across panels - all working (the other 3 tooltip panels), exception is the single equipped hands panel.&lt;/p&gt;

&lt;p&gt;Patch slot clearing and panel reuse logic for future nodes - slot clearning working as intended - no more infinite item loop.&lt;/p&gt;

&lt;p&gt;Need to reimport some more items , with all updated fields and data injected via .json importer - universal. &lt;/p&gt;

&lt;p&gt;✅ Status&lt;/p&gt;

&lt;p&gt;Unity 6.2 sealed&lt;/p&gt;

&lt;p&gt;Vulnerability purged&lt;/p&gt;

&lt;p&gt;Deprecated packages removed&lt;/p&gt;

&lt;p&gt;Runtime obeys&lt;/p&gt;

&lt;p&gt;IDE clean&lt;/p&gt;

&lt;p&gt;Loot panel system refactored&lt;/p&gt;

&lt;p&gt;Shrine speaks&lt;/p&gt;

&lt;p&gt;Debug fatigue lifted&lt;/p&gt;

&lt;p&gt;Tomorrow - &lt;/p&gt;

&lt;p&gt;Now with 300 followers, mostly bots and lurkers. The Discord server will be shared tomorrow, with a invite link.&lt;/p&gt;

&lt;p&gt;It is pretty basic, shares links to my dev logs , has some images from development, will attach video/s of my staring scene setup for progress reporting. Few channels active. A small insight to follow along behind the scenes if you are interested. I will share the invite as a separate dev log.   &lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>beginners</category>
      <category>devjournal</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
