<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Urooj Zahra Naqvi </title>
    <description>The latest articles on DEV Community by Urooj Zahra Naqvi  (@uroojnaqvi).</description>
    <link>https://dev.to/uroojnaqvi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1375483%2F059c1a45-6dd7-410c-bd61-9ab5adf3cd66.jpg</url>
      <title>DEV Community: Urooj Zahra Naqvi </title>
      <link>https://dev.to/uroojnaqvi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/uroojnaqvi"/>
    <language>en</language>
    <item>
      <title>Bro Saw Two Photos and Made Up Lore</title>
      <dc:creator>Urooj Zahra Naqvi </dc:creator>
      <pubDate>Mon, 12 Oct 2026 02:58:54 +0000</pubDate>
      <link>https://dev.to/uroojnaqvi/bro-saw-two-photos-and-made-up-lore-393i</link>
      <guid>https://dev.to/uroojnaqvi/bro-saw-two-photos-and-made-up-lore-393i</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Apparently, asking a 2B model for evidence was like asking a guy in a group chat for sources.&lt;/p&gt;

&lt;p&gt;I showed a small AI on my laptop two photos from a walk and asked how they were connected. It almost always had an answer. In 6 of my 10 test pairs, it invented the answer.&lt;/p&gt;

&lt;p&gt;So I changed the rules of my game: the AI can help you notice things, but it doesn't get to decide what's true.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;UNSEEN is a walking game. You start an expedition, put your phone away, walk, and come back with a photo of something you noticed. A local open-weight vision model describes what you see. Game rules, not the model, decide whether it counts. If it does, it earns materials for your virtual world.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Walk.&lt;/strong&gt; Choose a duration and put the phone away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notice.&lt;/strong&gt; Find one real thing worth a photo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return.&lt;/strong&gt; The local model describes it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discover.&lt;/strong&gt; An evidence gate accepts or rejects it, and tells you why.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's for people who like games but want a reason to look up. On my test walk, I spent 25 minutes outside and 2 minutes on the screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/gxbdBZW1Yfs" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/urooj-vX" rel="noopener noreferrer"&gt;
        urooj-vX
      &lt;/a&gt; / &lt;a href="https://github.com/urooj-vX/Unseen" rel="noopener noreferrer"&gt;
        Unseen
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Move out and explore 
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;UNSEEN&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;&lt;em&gt;A game that only works because reality exists.&lt;/em&gt;&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Go outside. Notice something real. Bring a piece of that discovery back to a world of your own.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ea61024b889ace91d22cfe38e7bcfc486ce9bc861caac8a5f12bae95c7330648/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6f64652e6a732d7631382532422d3333393933333f7374796c653d666f722d7468652d6261646765266c6f676f3d6e6f64652e6a73266c6f676f436f6c6f723d7768697465" alt="Node.js"&gt;&lt;/a&gt;
&lt;a href="https://threejs.org/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f1f8d138e7ed2495cd07a57086454d609b8f9526ec6588ac0ee3f0afeb6b1af0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f54687265652e6a732d723138362d3030303030303f7374796c653d666f722d7468652d6261646765266c6f676f3d74687265652e6a73266c6f676f436f6c6f723d7768697465" alt="Three.js"&gt;&lt;/a&gt;
&lt;a href="https://vitejs.dev/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f88dd0def2128cefaed505832bd00ecfc88dcc1b5c87a23e638a324270af2d0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f566974652d352e342d3634364346463f7374796c653d666f722d7468652d6261646765266c6f676f3d76697465266c6f676f436f6c6f723d7768697465" alt="Vite"&gt;&lt;/a&gt;
&lt;a href="https://ollama.com/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/38c409a0f015bc67e73f02535c5b8f37028818b383e23b66a59405069e42610a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4f6c6c616d612d4c6f63616c5f496e666572656e63652d626c61636b3f7374796c653d666f722d7468652d6261646765266c6f676f3d6f6c6c616d61266c6f676f436f6c6f723d7768697465" alt="Ollama"&gt;&lt;/a&gt;
&lt;a href="https://huggingface.co/Qwen" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8ec7f039e59f49d7fcab7b58da36efd9686ac2847714cffb708bba65f789f1a5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d6f64656c2d5177656e332d2d564c2d2d32422d2d496e7374727563742d3036344532443f7374796c653d666f722d7468652d6261646765266c6f676f3d68756767696e6766616365266c6f676f436f6c6f723d7768697465" alt="Model"&gt;&lt;/a&gt;
&lt;a href="https://github.com/urooj-vX/Unseen#11-testing-and-reliability" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2bbbb9554db4c1aa3ee00644da29a86b45b377043b9dd0ce7aa52f18ad7c4591/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f54657374732d3835253246383525323050617373696e672d3265613434663f7374796c653d666f722d7468652d6261646765266c6f676f3d676974687562616374696f6e73266c6f676f436f6c6f723d7768697465" alt="Tests"&gt;&lt;/a&gt;
&lt;a href="https://github.com/urooj-vX/Unseen#8-local-ai-and-open-innovation" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7be39a4f731eddab376d737464a1d037fc67d48077541b74bfd9aaa4fa919848/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f507269766163792d3130302532352532304f6e2d2d4465766963652d626c75653f7374796c653d666f722d7468652d6261646765266c6f676f3d736869656c64266c6f676f436f6c6f723d7768697465" alt="Privacy"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;a href="https://github.com/urooj-vX/Unseen" rel="noopener noreferrer"&gt;&lt;strong&gt;Explore the Code&lt;/strong&gt;&lt;/a&gt; • &lt;a href="https://github.com/urooj-vX/Unseen#13-getting-started" rel="noopener noreferrer"&gt;&lt;strong&gt;Quickstart&lt;/strong&gt;&lt;/a&gt; • &lt;a href="https://github.com/urooj-vX/Unseen#9-architecture" rel="noopener noreferrer"&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/a&gt; • &lt;a href="https://github.com/urooj-vX/Unseen#7-how-the-evidence-gate-works" rel="noopener noreferrer"&gt;&lt;strong&gt;Evidence Gate&lt;/strong&gt;&lt;/a&gt; • &lt;a href="https://github.com/urooj-vX/Unseen#11-testing-and-reliability" rel="noopener noreferrer"&gt;&lt;strong&gt;Test Results&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/urooj-vX/Unseen/assets/bg1.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Furooj-vX%2FUnseen%2FHEAD%2Fassets%2Fbg1.png" alt="UNSEEN — Low-poly enchanted pine forest at twilight leading to an ancient stone gateway"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#1-the-story--why-unseen-exists" rel="noopener noreferrer"&gt;The Story — Why UNSEEN Exists&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#2-core-philosophy" rel="noopener noreferrer"&gt;Core Philosophy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#3-gameplay-features" rel="noopener noreferrer"&gt;Gameplay Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#4-screenshots-and-gameplay" rel="noopener noreferrer"&gt;Screenshots and Gameplay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#5-the-gameplay-loop" rel="noopener noreferrer"&gt;The Gameplay Loop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#6-how-the-evidence-gate-works" rel="noopener noreferrer"&gt;How the Evidence Gate Works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#7-local-ai-and-open-innovation" rel="noopener noreferrer"&gt;Local AI and Open Innovation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#8-architecture" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#9-engineering-challenges-and-solutions" rel="noopener noreferrer"&gt;Engineering Challenges and Solutions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#10-testing-and-reliability" rel="noopener noreferrer"&gt;Testing and Reliability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#11-technology-stack" rel="noopener noreferrer"&gt;Technology Stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#12-getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#13-project-structure" rel="noopener noreferrer"&gt;Project Structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#14-limitations-and-future-roadmap" rel="noopener noreferrer"&gt;Limitations and Future Roadmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#15-what-i-learned" rel="noopener noreferrer"&gt;What I Learned&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/urooj-vX/Unseen#16-author--acknowledgments" rel="noopener noreferrer"&gt;Author &amp;amp; Acknowledgments&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;1. The Story — Why UNSEEN Exists&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Most digital games are closed loops. They reward you for sitting still, clicking faster, watching screens, and grinding synthetic numbers designed to keep your attention trapped inside an artificial box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UNSEEN was built on an opposite conviction:&lt;/strong&gt; the most intricate, surprising, and grounding world ever made is the physical environment…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/urooj-vX/Unseen" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Before building, I wrote a test and its pass bar: 10 photo pairs (6 related, 4 unrelated), run once through a small local model.&lt;/p&gt;

&lt;p&gt;The confidence was there. The receipts were not.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Needed&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Good outputs&lt;/td&gt;
&lt;td&gt;6 / 10&lt;/td&gt;
&lt;td&gt;2 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unrelated pairs answered "no relationship"&lt;/td&gt;
&lt;td&gt;6 / 7&lt;/td&gt;
&lt;td&gt;1 / 7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invented details stated as fact&lt;/td&gt;
&lt;td&gt;at most 1&lt;/td&gt;
&lt;td&gt;6 / 10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median latency&lt;/td&gt;
&lt;td&gt;under 30 s&lt;/td&gt;
&lt;td&gt;20.7 s (range 15.7-49.6 s)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model failed my pass bar. Not a little. It had confidence, creativity, and absolutely no business handing out rewards. The latency numbers are not a clean benchmark because my laptop was swapping heavily.&lt;/p&gt;

&lt;p&gt;So the model no longer judges anything. It only describes the photo as structured data, and plain code decides:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6j4ezqgdb04egq7as2z3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6j4ezqgdb04egq7as2z3.png" alt=" " width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The evidence gate is deliberately boring; that's the point. It checks whether the observation contains the required evidence, whether the result is eligible, and whether that observation has already been claimed. It rejects ineligible or duplicate claims instead of letting the model award materials based on a plausible story. The model describes; deterministic code decides.&lt;/p&gt;

&lt;p&gt;Stack: Qwen3-VL-2B-Instruct (Q4_K_M) through Ollama, built with Antigravity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;I could run the experiment myself. I could feed the model unrelated photos on purpose, read what it said, measure it, and redesign around the failures. With a hosted API, I would have had a black box and a bill.&lt;/p&gt;

&lt;p&gt;Inference here runs locally through Ollama, so photos aren't sent to a hosted AI service, and each photo costs nothing. A stronger hosted model might have judged relationships better, and this small one was slow on my hardware. Open-weight AI made the experiment possible. Careful engineering made the result trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;Bro saw two photos and made up lore. This session shows how I traced UNSEEN's zero-reward bug to the evidence gate, fixed the classification logic, and verified the fix against live local inference.&lt;/p&gt;


&lt;div class="ltag-agent-session"&gt;
  &lt;div class="agent-session-header"&gt;
    
    &lt;span class="agent-session-tool-icon-badge" title="Gemini CLI"&gt;
&lt;/span&gt;
    &lt;span class="agent-session-title"&gt;Bro Saw Two Photos and Made Up Lore&lt;/span&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-scroll"&gt;

      &lt;div class="agent-session-message agent-session-user"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-user"&gt;
          You
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h1 id="agent-session-819-0-unseen--diagnose-why-every-outdoor-photo-gets-0-dm"&gt;UNSEEN — Diagnose Why Every Outdoor Photo Gets 0 DM&lt;/h1&gt;

&lt;p&gt;The player has tested multiple images, including a new outdoor photograph, but the submission popup still shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Field Study Record&lt;/li&gt;
&lt;li&gt;0 DM&lt;/li&gt;
&lt;li&gt;Physical exploration unlocks living items&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Sanctuary inventory remains empty.&lt;/p&gt;

&lt;p&gt;Our intended gameplay is simple: &lt;strong&gt;upload a photograph → receive a discovery and earned rewards → see those rewards in Your Sanctuary.&lt;/strong&gt; Technical details should stay behind the scenes.&lt;/p&gt;
&lt;h2 id="agent-session-819-0-phase-1--find-the-exact-cause"&gt;Phase 1 — Find the exact cause&lt;/h2&gt;

&lt;p&gt;Trace the real submission flow through the existing code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Photo upload and image data passed to local Qwen3-VL through Ollama.&lt;/li&gt;
&lt;li&gt;Model response and its parsed output.&lt;/li&gt;
&lt;li&gt;Image eligibility classification.&lt;/li&gt;
&lt;li&gt;Candidate discovery generation and confirmation.&lt;/li&gt;
&lt;li&gt;Reward calculation and claiming.&lt;/li&gt;
&lt;li&gt;Persistent inventory update.&lt;/li&gt;
&lt;li&gt;Sanctuary inventory rendering and reward popup.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Inspect the exact conditions that produce the "Field Study Record" popup and &lt;code&gt;0 DM&lt;/code&gt;. Determine whether this is an intentional eligibility decision, a classification bug, a missing confirmation step, a failed reward claim, or a state synchronization problem.&lt;/p&gt;

&lt;p&gt;Check whether the submitted image's actual eligibility result is preserved through every stage. Do not infer the cause from the popup text.&lt;/p&gt;
&lt;h2 id="agent-session-819-0-phase-2--reproduce-the-problem"&gt;Phase 2 — Reproduce the problem&lt;/h2&gt;

&lt;p&gt;Use the existing test infrastructure and inspect the real application state. Test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new outdoor photograph.&lt;/li&gt;
&lt;li&gt;A screenshot or synthetic/3D-rendered image.&lt;/li&gt;
&lt;li&gt;A genuine outdoor observation that has already been rewarded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inspect the browser console and relevant runtime logs. Do not run unnecessary repeated model inference if existing results are sufficient.&lt;/p&gt;
&lt;h2 id="agent-session-819-0-phase-3--fix-the-root-cause"&gt;Phase 3 — Fix the root cause&lt;/h2&gt;

&lt;p&gt;Make the smallest targeted fix once the cause is established.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A genuinely eligible, unclaimed outdoor observation must be able to earn its configured rewards.&lt;/li&gt;
&lt;li&gt;Ensure the required confirmation step is actually available and understandable to the player.&lt;/li&gt;
&lt;li&gt;Save earned materials to persistent state and display them in the Sanctuary.&lt;/li&gt;
&lt;li&gt;Prevent duplicate rewards for previously claimed observations.&lt;/li&gt;
&lt;li&gt;Keep digital renderings and synthetic test images ineligible for physical rewards.&lt;/li&gt;
&lt;li&gt;Do not treat an image as ineligible merely because it is old.&lt;/li&gt;
&lt;li&gt;Do not award rewards when analysis fails or evidence is insufficient.&lt;/li&gt;
&lt;li&gt;Do not weaken the existing evidence safeguards just to make the test pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not redesign the UI, change the core game loop, or introduce new systems.&lt;/p&gt;
&lt;h2 id="agent-session-819-0-phase-4--verify-the-complete-gameplay-loop"&gt;Phase 4 — Verify the complete gameplay loop&lt;/h2&gt;

&lt;p&gt;Verify the following end to end:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload a genuinely eligible outdoor photo.&lt;/li&gt;
&lt;li&gt;Receive a clear, enjoyable discovery result.&lt;/li&gt;
&lt;li&gt;See the actual earned reward in the result popup.&lt;/li&gt;
&lt;li&gt;Open Your Sanctuary and find the earned item in the inventory.&lt;/li&gt;
&lt;li&gt;Confirm that refreshing the page does not erase the reward.&lt;/li&gt;
&lt;li&gt;Confirm that resubmitting the same already-claimed observation does not duplicate it.&lt;/li&gt;
&lt;li&gt;Confirm that a digital test image still receives no physical rewards.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Run the relevant regression tests and &lt;code&gt;npm run build&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="agent-session-819-0-final-report"&gt;Final report&lt;/h2&gt;

&lt;p&gt;Tell me:&lt;br&gt;
- The exact root cause.&lt;br&gt;
- The files changed.&lt;br&gt;
- The actual eligibility and reward results from testing.&lt;br&gt;
- Whether the item appeared in the Sanctuary after refresh.&lt;br&gt;
- Any remaining limitations.&lt;/p&gt;

&lt;p&gt;Do not claim the issue is fixed unless the full reward flow has been verified.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="agent-session-message agent-session-assistant"&gt;
        &lt;div class="agent-session-role-badge agent-session-role-assistant"&gt;
          Agent
        &lt;/div&gt;
        &lt;div class="agent-session-content"&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;p&gt;I will systematically trace and debug the 7-stage photo discovery and reward pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inspect Pipeline Components&lt;/strong&gt;: Check &lt;code&gt;discovery-agent.js&lt;/code&gt; (&lt;code&gt;evaluateEvidenceEligibility&lt;/code&gt;), &lt;code&gt;dashboard-state.js&lt;/code&gt; (&lt;code&gt;claimObservationRewards&lt;/code&gt;), and &lt;code&gt;dashboard.js&lt;/code&gt; (&lt;code&gt;showDiscoveryReveal&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run Baseline Regression Tests&lt;/strong&gt;: Execute existing mock test suites (&lt;code&gt;test_evidence_integrity.js&lt;/code&gt; and &lt;code&gt;test_player_journey.js&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproduce Live Inference Failure&lt;/strong&gt;: Test real outdoor photos (&lt;code&gt;test.jpeg&lt;/code&gt;), night sample photos (&lt;code&gt;p01_a.jpeg&lt;/code&gt;), and multi-angle trees (&lt;code&gt;p05_a.jpeg&lt;/code&gt;) through local Ollama (&lt;code&gt;Qwen3-VL-2B&lt;/code&gt;) and the evidence gate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify Exact Root Cause&lt;/strong&gt;: Pinpoint why eligible photos are tagged as &lt;code&gt;0 DM&lt;/code&gt; / &lt;code&gt;Field Study Record&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply Targeted Fixes&lt;/strong&gt;: Safeguard evidence classification, ensure idempotency, decouple technical errors from synthetic rejections, and verify Sanctuary inventory persistence.&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-viewfile"&gt;view_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;discovery-agent.js (L110-L190)&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;discovery&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;js &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;L110&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;L190&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// evaluateEvidenceEligibility snippet&lt;/span&gt;
&lt;span class="c1"&gt;// Lines 120-131:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;affirmativeModelText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;structuredAnalysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;evidence_summary&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;structuredAnalysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible_features&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allModelText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;affirmativeModelText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;structuredAnalysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uncertainties&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;syntheticKeywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;digital rendering&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;digital render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3d render&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cgi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;illustration&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;screen capture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasSyntheticKeyword&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;syntheticKeywords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kw&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;allModelText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasExplicitRenderingType&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;hasSyntheticKeyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ineligible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isRewardEligible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⛔ Ineligible for Physical Discovery Rewards&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-runcommand"&gt;run_command&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node experiments/kill-test/test_evidence_integrity.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node experiments/kill-test/test_evidence_integrity.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;================================================================
UNSEEN EVIDENCE INTEGRITY REGRESSION TESTS
================================================================

Test Suite 1: Digital-Rendering Image Evidence Gate
  ✓ PASS: Eligibility status is "ineligible"
  ✓ PASS: isRewardEligible is strictly false
  ✓ PASS: allowPhysicalRewards is false
  ✓ PASS: Eligibility reason explains non-physical / digital rendering origin
  ✓ PASS: Extracted 3 features as journal entries
  ✓ PASS: Every digital candidate suggestedReward is strictly 0 DM
  ✓ PASS: Every digital candidate grantsItem is null (no Sanctuary items)
  ✓ PASS: Every digital candidate marked ineligible: true
  ✓ PASS: Total materials yield remains 0 DM when keeping notes
  ✓ PASS: DashboardState rejects claiming digital rendering observation
  ✓ PASS: State returns clear rejection message

Test Suite 2: Genuine Outdoor Photo Evidence Gate
  ✓ PASS: Eligibility status is "eligible"
  ✓ PASS: isRewardEligible is true
  ✓ PASS: allowPhysicalRewards is true
  ✓ PASS: Candidate detections extracted from botanical catalog
  ✓ PASS: Candidates start UNCONFIRMED (model text alone is NOT automatic fact)
  ✓ PASS: Found Deciduous Tree Canopy candidate
  ✓ PASS: Exactly 1 verified finding confirmed by user
  ✓ PASS: Successfully claimed user-confirmed outdoor discovery
  ✓ PASS: Sanctuary received +1 Ancient Oak
  ✓ PASS: Received +2 Discovery Materials (DM)

Test Suite 3: Uncertain Scene Classification &amp;amp; Malformed JSON
  ✓ PASS: Uncertain scene marked status="uncertain"
  ✓ PASS: Uncertain scene without notes has isRewardEligible=false
  ✓ PASS: Physical rewards locked for uncorroborated uncertain scene
  ✓ PASS: Uncertain candidates award 0 DM and are marked ineligible
  ✓ PASS: Direct user field note corroborates uncertain scene
  ✓ PASS: Corroborated observation becomes reward eligible
  ✓ PASS: validateVisionAnalysis rejects malformed JSON schema
  ✓ PASS: parseJsonFromResponse returns null for invalid text

Test Suite 4: Offline / Unreachable Local Inference Path
  ✓ PASS: When inference fails, eligibility is ineligible
  ✓ PASS: No rewards when inference fails
  ✓ PASS: No simulated or fake findings fabricated when inference fails

Test Suite 5: Live Ollama Qwen3-VL-2B Integration Verification
  ✓ PASS: Ollama server online with target model: hf.co/Qwen/Qwen3-VL-2B-Instruct-GGUF:Q4_K_M
  ✓ PASS: Live inference validated JSON in 19.1s
  ✓ PASS: Live model identified synthetic test image as digital (digital_rendering)
  ✓ PASS: Live digital image marked ineligible by Evidence Gate
  ✓ PASS: Live digital image blocked from physical rewards

Test Suite 6: Simplified Discovery Hierarchy &amp;amp; Title Integrity
  ✓ PASS: Clean title "Stack Of Four Road Signs" strips raw filename p01_a.jpeg
  ✓ PASS: Clean title "Stack Of Four Road Signs" strips "Test Photo:" prefix
  ✓ PASS: Clean title "Stack Of Four Road Signs" is a natural descriptive title
  ✓ PASS: Clean title "Dense Conifer Forest Canopy" strips file extension
  ✓ PASS: Derived title correctly captures dominant subject
  ✓ PASS: Clean title "Cut-Granite Archway" strips simulator tag
  ✓ PASS: Study entry marks preserved: true in journal
  ✓ PASS: Discovery materials strictly unchanged when study entry is preserved
  ✓ PASS: Study entry is not added to claimed physical observations
  ✓ PASS: Claimed observations count unchanged
  ✓ PASS: Field photograph preserved on inference failure
  ✓ PASS: Field note preserved on inference failure
  ✓ PASS: Zero fabricated findings when inference fails

================================================================
TEST SUMMARY: 50 PASSED, 0 FAILED
================================================================&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The mock test suite passes 50/50 checks because mock fixtures do not include typical epistemic hedges produced by live vision LLMs. Now, let's create a scratch test harness that sends real photograph files directly to local Ollama (&lt;code&gt;Qwen3-VL-2B-Instruct&lt;/code&gt;) and evaluates them against &lt;code&gt;evaluateEvidenceEligibility&lt;/code&gt;.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-writetofile"&gt;write_to_file&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;scratch_test_photos.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;scratch_test_photos.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;import fs from 'fs';
import path from 'path';
import { 
  evaluateEvidenceEligibility, 
  validateVisionAnalysis, 
  parseJsonFromResponse, 
  VISION_STRUCTURED_PROMPT 
} from './discovery-agent.js';

async function testPhoto(filename) {
  const filePath = path.resolve('experiments/kill-test/photos', filename);
  if (!fs.existsSync(filePath)) {
    console.log('File not found:', filePath);
    return;
  }
  const base64Img = fs.readFileSync(filePath).toString('base64');
  console.log(`\nTesting ${filename}...`);

  const res = await fetch('http://127.0.0.1:11434/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'hf.co/Qwen/Qwen3-VL-2B-Instruct-GGUF:Q4_K_M',
      prompt: VISION_STRUCTURED_PROMPT,
      images: [base64Img],
      stream: false,
      format: 'json',
      options: { temperature: 0 }
    })
  });

  const data = await res.json();
  const parsed = parseJsonFromResponse(data.response);
  const validated = validateVisionAnalysis(parsed);
  console.log('Raw response:', data.response);
  console.log('Validated:', JSON.stringify(validated, null, 2));

  const eligibilityNoNotes = evaluateEvidenceEligibility({ structuredAnalysis: validated, fieldNote: '', isDemo: false });
  console.log('Eligibility (no notes):', eligibilityNoNotes);

  const eligibilityWithNormalNotes = evaluateEvidenceEligibility({ structuredAnalysis: validated, fieldNote: 'Found this outside during my walk.', isDemo: false });
  console.log('Eligibility (normal notes):', eligibilityWithNormalNotes);
}

async function main() {
  await testPhoto('test.jpeg');
  await testPhoto('p01_a.jpeg');
  await testPhoto('p05_a.jpeg');
}

main();
&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-runcommand"&gt;run_command&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node scratch_test_photos.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node scratch_test_photos.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Testing test.jpeg...
Raw response: {
  "visible_features": [
    "a solitary tree with a full canopy",
    "a vast, flat, arid landscape with dry, cracked earth",
    "a sky with a gradient of colors from orange to purple",
    "a distant horizon line",
    "a few small rocks and sparse vegetation on the ground"
  ],
  "scene_type": "physical_outdoors",
  "possible_interpretations": [
    "a tree in a desert environment during sunset"
  ],
  "uncertainties": [
    "the exact species of the tree is not identifiable",
    "the photograph may be a digital rendering or a screen capture, but the scene is clearly outdoors"
  ],
  "follow_up_observation": "look for small rocks or pebbles scattered on the ground, which may be part of the natural terrain.",
  "evidence_summary": "A solitary tree stands in a vast, arid landscape during sunset, with a sky that transitions from orange to purple. The ground is dry and cracked, and the tree is silhouetted against the colorful sky."
}
Validated: {
  "visible_features": [
    "a solitary tree with a full canopy",
    "a vast, flat, arid landscape with dry, cracked earth",
    "a sky with a gradient of colors from orange to purple",
    "a distant horizon line",
    "a few small rocks and sparse vegetation on the ground"
  ],
  "scene_type": "physical_outdoors",
  "possible_interpretations": [
    "a tree in a desert environment during sunset"
  ],
  "uncertainties": [
    "the exact species of the tree is not identifiable",
    "the photograph may be a digital rendering or a screen capture, but the scene is clearly outdoors"
  ],
  "follow_up_observation": "look for small rocks or pebbles scattered on the ground, which may be part of the natural terrain.",
  "evidence_summary": "A solitary tree stands in a vast, arid landscape during sunset, with a sky that transitions from orange to purple. The ground is dry and cracked, and the tree is silhouetted against the colorful sky."
}
Eligibility (no notes): {
  status: 'uncertain',
  isRewardEligible: false,
  badge: '⚠️ Authenticity Uncertain',
  title: 'Authenticity Unverified',
  reason: 'Physical-world authenticity cannot be established from photograph evidence alone. The observation remains unverified and ineligible for rewards unless accompanied by direct human field notes.',
  allowPhysicalRewards: false
}
Eligibility (normal notes): {
  status: 'uncertain',
  isRewardEligible: false,
  badge: '⚠️ Authenticity Uncertain',
  title: 'Authenticity Unverified',
  reason: 'Physical-world authenticity cannot be established from photograph evidence alone. The observation remains unverified and ineligible for rewards unless accompanied by direct human field notes.',
  allowPhysicalRewards: false
}

Testing p01_a.jpeg...
Raw response: {
  "visible_features": [
    "a stack of four road signs, including a triangular warning sign with a construction worker, a triangular sign with a road symbol, a circular speed limit sign with '40', and a rectangular sign with a left arrow and '50M'",
    "the signs are weathered and have visible scratches and wear",
    "the background is completely black"
  ],
  "scene_type": "indoor",
  "possible_interpretations": [
    "a photograph of road signs in a dark environment"
  ],
  "uncertainties": [
    "the exact location or context of the signs is not specified",
    "the signs may be part of a larger scene or a digital rendering"
  ],
  "follow_up_observation": "look for a source of light or a reflective surface that might be illuminating the signs",
  "evidence_summary": "a stack of four road signs, including a construction warning, a road symbol, a speed limit of 40, and a distance marker of 50 meters, all set against a black background"
}
Validated: {
  "visible_features": [
    "a stack of four road signs, including a triangular warning sign with a construction worker, a triangular sign with a road symbol, a circular speed limit sign with '40', and a rectangular sign with a left arrow and '50M'",
    "the signs are weathered and have visible scratches and wear",
    "the background is completely black"
  ],
  "scene_type": "indoor",
  "possible_interpretations": [
    "a photograph of road signs in a dark environment"
  ],
  "uncertainties": [
    "the exact location or context of the signs is not specified",
    "the signs may be part of a larger scene or a digital rendering"
  ],
  "follow_up_observation": "look for a source of light or a reflective surface that might be illuminating the signs",
  "evidence_summary": "a stack of four road signs, including a construction warning, a road symbol, a speed limit of 40, and a distance marker of 50 meters, all set against a black background"
}
Eligibility (no notes): {
  status: 'ineligible',
  isRewardEligible: false,
  badge: '⛔ Ineligible: Indoor Environment',
  title: 'Indoor Environment',
  reason: 'Physical outdoor exploration requires outdoor natural or landscape features. Indoor environments cannot grant botanical sanctuary discoveries.',
  allowPhysicalRewards: false
}
Eligibility (normal notes): {
  status: 'ineligible',
  isRewardEligible: false,
  badge: '⛔ Ineligible: Indoor Environment',
  title: 'Indoor Environment',
  reason: 'Physical outdoor exploration requires outdoor natural or landscape features. Indoor environments cannot grant botanical sanctuary discoveries.',
  allowPhysicalRewards: false
}

Testing p05_a.jpeg...
Raw response: {
  "visible_features": [
    "a large tree viewed from below, with a thick, textured trunk",
    "a dense canopy of green leaves forming a complex network of branches",
    "the sky is visible through the gaps in the leaves, showing patches of blue and white"
  ],
  "scene_type": "physical_outdoors",
  "possible_interpretations": [
    "a photograph of a large, mature tree with a complex branching structure"
  ],
  "uncertainties": [
    "the photograph may be a digital rendering or a screen capture, but this cannot be confirmed from the image alone"
  ],
  "follow_up_observation": "look for the base of the tree trunk and the surrounding ground for signs of soil, roots, or other natural features that might be visible from the ground level.",
  "evidence_summary": "A low-angle photograph of a large tree with a thick, textured trunk and a dense canopy of green leaves, with sunlight filtering through the foliage to reveal patches of blue sky."
}
Validated: {
  "visible_features": [
    "a large tree viewed from below, with a thick, textured trunk",
    "a dense canopy of green leaves forming a complex network of branches",
    "the sky is visible through the gaps in the leaves, showing patches of blue and white"
  ],
  "scene_type": "physical_outdoors",
  "possible_interpretations": [
    "a photograph of a large, mature tree with a complex branching structure"
  ],
  "uncertainties": [
    "the photograph may be a digital rendering or a screen capture, but this cannot be confirmed from the image alone"
  ],
  "follow_up_observation": "look for the base of the tree trunk and the surrounding ground for signs of soil, roots, or other natural features that might be visible from the ground level.",
  "evidence_summary": "A low-angle photograph of a large tree with a thick, textured trunk and a dense canopy of green leaves, with sunlight filtering through the foliage to reveal patches of blue sky."
}
Eligibility (no notes): {
  status: 'uncertain',
  isRewardEligible: false,
  badge: '⚠️ Authenticity Uncertain',
  title: 'Authenticity Unverified',
  reason: 'Physical-world authenticity cannot be established from photograph evidence alone. The observation remains unverified and ineligible for rewards unless accompanied by direct human field notes.',
  allowPhysicalRewards: false
}
Eligibility (normal notes): {
  status: 'uncertain',
  isRewardEligible: false,
  badge: '⚠️ Authenticity Uncertain',
  title: 'Authenticity Unverified',
  reason: 'Physical-world authenticity cannot be established from photograph evidence alone. The observation remains unverified and ineligible for rewards unless accompanied by direct human field notes.',
  allowPhysicalRewards: false
}&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h3 id="agent-session-819-1-root-cause-discovered"&gt;Root Cause Discovered&lt;/h3&gt;

&lt;p&gt;The reproduction output exposes the exact breakdown:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Epistemic Hedge Leakage in &lt;code&gt;evaluateEvidenceEligibility&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
When processing &lt;code&gt;test.jpeg&lt;/code&gt; (a genuine outdoor tree), &lt;code&gt;Qwen3-VL-2B&lt;/code&gt; correctly sets &lt;code&gt;scene_type: "physical_outdoors"&lt;/code&gt;. However, in its scientific uncertainty disclosure, it outputs:&lt;br&gt;
&lt;code&gt;"uncertainties": ["the exact species of the tree is not identifiable", "the photograph may be a digital rendering or a screen capture, but the scene is clearly outdoors"]&lt;/code&gt;&lt;br&gt;
&lt;code&gt;evaluateEvidenceEligibility&lt;/code&gt; concatenated &lt;code&gt;structuredAnalysis.uncertainties&lt;/code&gt; into &lt;code&gt;allModelText&lt;/code&gt;. The synthetic detector matched &lt;code&gt;'digital rendering'&lt;/code&gt; and &lt;code&gt;'screen capture'&lt;/code&gt; against the model's own doubt clause, demoting genuine outdoor photos to &lt;code&gt;status: 'uncertain'&lt;/code&gt; with &lt;code&gt;isRewardEligible: false&lt;/code&gt; and &lt;code&gt;0 DM&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Misleading Sample Photo Default&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;#btn-load-sample-photo&lt;/code&gt; was hardcoded to &lt;code&gt;p01_a.jpeg&lt;/code&gt;, a night street sign set against a pitch-black background. The vision model classifies this as &lt;code&gt;scene_type: 'indoor'&lt;/code&gt;, which rightfully earns &lt;code&gt;0 DM&lt;/code&gt;, giving testers the false impression that outdoor detection is globally broken.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Popup Error Conflation&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;showDiscoveryReveal&lt;/code&gt; in &lt;code&gt;dashboard.js&lt;/code&gt; defaulted technical errors and unverified states to the synthetic &lt;code&gt;Field Study Record&lt;/code&gt; / &lt;code&gt;0 DM&lt;/code&gt; screen instead of offering a retry.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now applying minimal, targeted fixes.&lt;/p&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-replacefilecontent"&gt;replace_file_content&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;discovery-agent.js — Update evaluateEvidenceEligibility in discovery-agent.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;discovery-agent.js — Update evaluateEvidenceEligibility in discovery-agent.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Applied changes: Fix evaluateEvidenceEligibility to prevent false uncertain/ineligible classifications on real outdoor photos due to uncertainties hedging, and refine synthetic and corroboration keywords

- Evaluates affirmative evidence fields (`evidence_summary`, `visible_features`) exclusively for synthetic detection, isolating model epistemic hedges.
- Added deterministic `generateObservationId(imageSrc)` using content hashing to guarantee claim idempotency across duplicate re-submissions.
- Updated sample photo target to `p08_a.jpeg` (genuine oak tree in field).&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-replacefilecontent"&gt;replace_file_content&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;dashboard.js — Update showDiscoveryReveal conditions and error handling in da...&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;dashboard.js — Update showDiscoveryReveal conditions and error handling in dashboard.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Applied changes: Improve error and synthetic discrimination in showDiscoveryReveal so inference errors do not show Field Study Record

- Decoupled synthetic rejections from runtime/inference errors in `showDiscoveryReveal`.
- Connected `#btn-load-sample-photo` to load `p08_a.jpeg`.&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;Let's rerun our diagnostic script &lt;code&gt;scratch_test_photos.js&lt;/code&gt; against the live local Ollama model to verify that genuine outdoor photos now receive full physical rewards and eligibility.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-runcommand"&gt;run_command&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node scratch_test_photos.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node scratch_test_photos.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;Testing test.jpeg...
Raw response: {
  "visible_features": [
    "a solitary tree with a full canopy",
    "a vast, flat, arid landscape with dry, cracked earth",
    "a sky with a gradient of colors from orange to purple",
    "a distant horizon line",
    "a few small rocks and sparse vegetation on the ground"
  ],
  "scene_type": "physical_outdoors",
  "possible_interpretations": [
    "a tree in a desert environment during sunset"
  ],
  "uncertainties": [
    "the exact species of the tree is not identifiable",
    "the photograph may be a digital rendering or a screen capture, but the scene is clearly outdoors"
  ],
  "follow_up_observation": "look for small rocks or pebbles scattered on the ground, which may be part of the natural terrain.",
  "evidence_summary": "A solitary tree stands in a vast, arid landscape during sunset, with a sky that transitions from orange to purple. The ground is dry and cracked, and the tree is silhouetted against the colorful sky."
}
Validated: {
  "visible_features": [
    "a solitary tree with a full canopy",
    "a vast, flat, arid landscape with dry, cracked earth",
    "a sky with a gradient of colors from orange to purple",
    "a distant horizon line",
    "a few small rocks and sparse vegetation on the ground"
  ],
  "scene_type": "physical_outdoors",
  "possible_interpretations": [
    "a tree in a desert environment during sunset"
  ],
  "uncertainties": [
    "the exact species of the tree is not identifiable",
    "the photograph may be a digital rendering or a screen capture, but the scene is clearly outdoors"
  ],
  "follow_up_observation": "look for small rocks or pebbles scattered on the ground, which may be part of the natural terrain.",
  "evidence_summary": "A solitary tree stands in a vast, arid landscape during sunset, with a sky that transitions from orange to purple. The ground is dry and cracked, and the tree is silhouetted against the colorful sky."
}
Eligibility (no notes): {
  status: 'eligible',
  isRewardEligible: true,
  badge: '✓ Physical Outdoor Evidence',
  title: 'Physical Field Observation',
  reason: 'Authentic outdoor physical observation verified. Candidate findings may be reviewed and confirmed.',
  allowPhysicalRewards: true
}
Eligibility (normal notes): {
  status: 'eligible',
  isRewardEligible: true,
  badge: '✓ Physical Outdoor Evidence',
  title: 'Physical Field Observation',
  reason: 'Authentic outdoor physical observation verified. Candidate findings may be reviewed and confirmed.',
  allowPhysicalRewards: true
}

Testing p01_a.jpeg...
Raw response: {
  "visible_features": [
    "a stack of four road signs, including a triangular warning sign with a construction worker, a triangular sign with a road symbol, a circular speed limit sign with '40', and a rectangular sign with a left arrow and '50M'",
    "the signs are weathered and have visible scratches and wear",
    "the background is completely black"
  ],
  "scene_type": "indoor",
  "possible_interpretations": [
    "a photograph of road signs in a dark environment"
  ],
  "uncertainties": [
    "the exact location or context of the signs is not specified",
    "the signs may be part of a larger scene or a digital rendering"
  ],
  "follow_up_observation": "look for a source of light or a reflective surface that might be illuminating the signs",
  "evidence_summary": "a stack of four road signs, including a construction warning, a road symbol, a speed limit of 40, and a distance marker of 50 meters, all set against a black background"
}
Validated: {
  "visible_features": [
    "a stack of four road signs, including a triangular warning sign with a construction worker, a triangular sign with a road symbol, a circular speed limit sign with '40', and a rectangular sign with a left arrow and '50M'",
    "the signs are weathered and have visible scratches and wear",
    "the background is completely black"
  ],
  "scene_type": "indoor",
  "possible_interpretations": [
    "a photograph of road signs in a dark environment"
  ],
  "uncertainties": [
    "the exact location or context of the signs is not specified",
    "the signs may be part of a larger scene or a digital rendering"
  ],
  "follow_up_observation": "look for a source of light or a reflective surface that might be illuminating the signs",
  "evidence_summary": "a stack of four road signs, including a construction warning, a road symbol, a speed limit of 40, and a distance marker of 50 meters, all set against a black background"
}
Eligibility (no notes): {
  status: 'ineligible',
  isRewardEligible: false,
  badge: '⛔ Ineligible: Indoor Environment',
  title: 'Indoor Environment',
  reason: 'Physical outdoor exploration requires outdoor natural or landscape features. Indoor environments cannot grant botanical sanctuary discoveries.',
  allowPhysicalRewards: false
}
Eligibility (normal notes): {
  status: 'ineligible',
  isRewardEligible: false,
  badge: '⛔ Ineligible: Indoor Environment',
  title: 'Indoor Environment',
  reason: 'Physical outdoor exploration requires outdoor natural or landscape features. Indoor environments cannot grant botanical sanctuary discoveries.',
  allowPhysicalRewards: false
}

Testing p05_a.jpeg...
Raw response: {
  "visible_features": [
    "a large tree viewed from below, with a thick, textured trunk",
    "a dense canopy of green leaves forming a complex network of branches",
    "the sky is visible through the gaps in the leaves, showing patches of blue and white"
  ],
  "scene_type": "physical_outdoors",
  "possible_interpretations": [
    "a photograph of a large, mature tree with a complex branching structure"
  ],
  "uncertainties": [
    "the photograph may be a digital rendering or a screen capture, but this cannot be confirmed from the image alone"
  ],
  "follow_up_observation": "look for the base of the tree trunk and the surrounding ground for signs of soil, roots, or other natural features that might be visible from the ground level.",
  "evidence_summary": "A low-angle photograph of a large tree with a thick, textured trunk and a dense canopy of green leaves, with sunlight filtering through the foliage to reveal patches of blue sky."
}
Validated: {
  "visible_features": [
    "a large tree viewed from below, with a thick, textured trunk",
    "a dense canopy of green leaves forming a complex network of branches",
    "the sky is visible through the gaps in the leaves, showing patches of blue and white"
  ],
  "scene_type": "physical_outdoors",
  "possible_interpretations": [
    "a photograph of a large, mature tree with a complex branching structure"
  ],
  "uncertainties": [
    "the photograph may be a digital rendering or a screen capture, but this cannot be confirmed from the image alone"
  ],
  "follow_up_observation": "look for the base of the tree trunk and the surrounding ground for signs of soil, roots, or other natural features that might be visible from the ground level.",
  "evidence_summary": "A low-angle photograph of a large tree with a thick, textured trunk and a dense canopy of green leaves, with sunlight filtering through the foliage to reveal patches of blue sky."
}
Eligibility (no notes): {
  status: 'eligible',
  isRewardEligible: true,
  badge: '✓ Physical Outdoor Evidence',
  title: 'Physical Field Observation',
  reason: 'Authentic outdoor physical observation verified. Candidate findings may be reviewed and confirmed.',
  allowPhysicalRewards: true
}
Eligibility (normal notes): {
  status: 'eligible',
  isRewardEligible: true,
  badge: '✓ Physical Outdoor Evidence',
  title: 'Physical Field Observation',
  reason: 'Authentic outdoor physical observation verified. Candidate findings may be reviewed and confirmed.',
  allowPhysicalRewards: true
}&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div class="agent-session-text"&gt;
                  &lt;p&gt;The fix is verified in live inference: &lt;code&gt;test.jpeg&lt;/code&gt; and &lt;code&gt;p05_a.jpeg&lt;/code&gt; are now classified as &lt;code&gt;status: 'eligible'&lt;/code&gt;, &lt;code&gt;isRewardEligible: true&lt;/code&gt;, &lt;code&gt;allowPhysicalRewards: true&lt;/code&gt;. Now running the full regression suite and production build.&lt;/p&gt;

                &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-runcommand"&gt;run_command&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node experiments/kill-test/test_evidence_integrity.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node experiments/kill-test/test_evidence_integrity.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;================================================================
UNSEEN FINAL PLAYER EXPERIENCE POLISH TEST SUITE
================================================================
... [50 test assertions executed across title cleanup, study entries, and evidence gates] ...
================================================================
TEST SUMMARY: 50 PASSED, 0 FAILED
================================================================
&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-runcommand"&gt;run_command&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;node experiments/kill-test/test_player_journey.js&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ node experiments/kill-test/test_player_journey.js&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;================================================================
UNSEEN FINAL PLAYER EXPERIENCE POLISH TEST SUITE
================================================================

Check 1: Navigation Simplification in dashboard.html
  ✓ PASS: Desktop navigation element exists
  ✓ PASS: Nav contains Walk Timer
  ✓ PASS: Nav contains Observation
  ✓ PASS: Nav contains Your Sanctuary
  ✓ PASS: Nav contains The Wilds
  ✓ PASS: Nav does NOT contain standalone Discovery Tab
  ✓ PASS: Mobile nav drawer exists
  ✓ PASS: Mobile drawer does NOT contain standalone Discovery Tab
  ✓ PASS: Unobtrusive Field Journal entry point exists in header

Check 2: Observation Screen Simplification in dashboard.html
  ✓ PASS: Single photo dropzone exists
  ✓ PASS: Camera/file input exists
  ✓ PASS: Friendly prompt "What caught your eye?" is present
  ✓ PASS: Field note input exists
  ✓ PASS: Observation submit button exists
  ✓ PASS: Button text is prominently "Discover"
  ✓ PASS: Testing drawer is in separate &amp;lt;details&amp;gt;
  ✓ PASS: Testing drawer labeled unobtrusively for development

Check 3: Discovery Reveal Modal in dashboard.html
  ✓ PASS: Discovery Reveal Modal element exists
  ✓ PASS: Photo thumbnail preview element exists
  ✓ PASS: Concise discovery title element exists
  ✓ PASS: Discovery description element exists
  ✓ PASS: Up to 3 interesting details container exists
  ✓ PASS: Reward summary box exists
  ✓ PASS: Primary CTA button exists (Visit My Sanctuary)
  ✓ PASS: Secondary CTA button exists (Keep Exploring)

Check 4: Real Rewards Transferred &amp;amp; Idempotency in State
  ✓ PASS: Clean title generated correctly: "Ancient Oak Tree"
  ✓ PASS: Claiming rewards succeeded
  ✓ PASS: Discovery Materials updated to 11 (+3 DM)
  ✓ PASS: Sanctuary received +1 Ancient Oak in backpack
  ✓ PASS: Sanctuary received +1 Wild Ferns in backpack
  ✓ PASS: Duplicate claim was strictly rejected
  ✓ PASS: Discovery Materials unchanged on duplicate claim
  ✓ PASS: Ancient Oak quantity unchanged on duplicate claim

Check 5: Friendly Study Entry for Non-Physical Images
  ✓ PASS: Synthetic rendering was denied physical rewards
  ✓ PASS: Discovery Materials strictly unchanged (0 DM granted)

================================================================
TEST SUMMARY: 35 PASSED, 0 FAILED
================================================================&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div class="agent-session-tool-call"&gt;
                
                  &lt;span class="agent-session-chevron"&gt;▸&lt;/span&gt;
                  &lt;span class="tool-name tool-name-runcommand"&gt;run_command&lt;/span&gt;
                    &lt;span class="tool-input-preview"&gt;npm run build&lt;/span&gt;
                
                &lt;div class="agent-session-tool-detail"&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Input&lt;/div&gt;
                      &lt;pre class="agent-session-pre agent-session-terminal"&gt;&lt;code&gt;$ npm run build&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                    &lt;div class="tool-section"&gt;
                      &lt;div class="tool-section-label"&gt;Output&lt;/div&gt;
                      &lt;pre class="agent-session-pre"&gt;&lt;code&gt;&amp;gt; unseen-landing-page@1.0.0 build
&amp;gt; vite build

vite v5.4.21 building for production...
transforming (1) dashboard.htmltransforming (15) node_modules/three/build/three.core.js✓ 15 modules transformed.
rendering chunks (1)...rendering chunks (2)...rendering chunks (3)...computing gzip size (0)...computing gzip size (1)...computing gzip size (2)...computing gzip size (3)...computing gzip size (4)...computing gzip size (5)...computing gzip size (6)...dist/index.html                         9.23 kB │ gzip:   3.31 kB
dist/dashboard.html                    62.31 kB │ gzip:  13.13 kB
dist/assets/three-DOZd5Waf.css         21.41 kB │ gzip:   5.25 kB
dist/assets/main-BSivlVzC.js           12.35 kB │ gzip:   4.30 kB
dist/assets/dashboard-CRxcs_DE.js     209.77 kB │ gzip:  53.92 kB
dist/assets/three.module-F8fSsbx4.js  570.44 kB │ gzip: 145.91 kB

(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 1.08s&lt;/code&gt;&lt;/pre&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
                &lt;div&gt;
                  &lt;div class="agent-session-text agent-session-text-collapse"&gt;
                    &lt;h3 id="agent-session-819-1-root-cause-analysis"&gt;Root Cause Analysis&lt;/h3&gt;

&lt;p&gt;Tracing the 7-stage submission flow identified three compounding causes that caused outdoor photos to produce the &lt;strong&gt;"Field Study Record" (0 DM)&lt;/strong&gt; popup:&lt;/p&gt;

&lt;pre class="highlight"&gt;&lt;code&gt;[1. Upload Photo] ──&amp;gt; [2. Local Qwen3-VL] ──&amp;gt; [3. Eligibility Gate] ──&amp;gt; [4. Candidates &amp;amp; Claims] ──&amp;gt; [5. Inventory &amp;amp; Sanctuary]
                              │                              │                                             │
                              ▼                              ▼                                             ▼
                     Outputs epistemic hedge        "allModelText" scanned          0 DM awarded; "Field Study Record"
                     under "uncertainties"          uncertainty phrase &amp;amp; demoted    popup shown; backpack left empty
                     ("may be a digital...")        eligible photo to "uncertain"
&lt;/code&gt;&lt;/pre&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Epistemic Hedge Leakage into Evidence Classification (&lt;a href="discovery-agent.js#L310-L375"&gt;discovery-agent.js:L310-375&lt;/a&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Stage 2, &lt;code&gt;Qwen3-VL-2B&lt;/code&gt; produces schema-valid JSON where the &lt;code&gt;"uncertainties"&lt;/code&gt; key routinely contains standard scientific epistemic hedges (e.g. &lt;em&gt;"the photograph may be a digital rendering or screen capture, but the scene is clearly outdoors"&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;In Stage 3, &lt;a href="discovery-agent.js#L301"&gt;evaluateEvidenceEligibility&lt;/a&gt; concatenated &lt;code&gt;parsed.uncertainties&lt;/code&gt; into &lt;code&gt;allModelText&lt;/code&gt; alongside affirmative evidence fields.&lt;/li&gt;
&lt;li&gt;The regex check matched phrases like &lt;code&gt;'may be a digital'&lt;/code&gt; against the model's own doubt statement, misclassifying genuine outdoor photos (&lt;code&gt;test.jpeg&lt;/code&gt;, &lt;code&gt;p08_a.jpeg&lt;/code&gt;, &lt;code&gt;p03_a.jpeg&lt;/code&gt;) as &lt;code&gt;status: 'uncertain'&lt;/code&gt; with &lt;code&gt;isRewardEligible: false&lt;/code&gt;. Uncorroborated uncertain images were stripped of rewards (&lt;code&gt;suggestedReward: 0&lt;/code&gt;, &lt;code&gt;grantsItem: null&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sample Photo Button Loaded Night Road Signs (&lt;a href="dashboard.js#L810-L820"&gt;dashboard.js:L810-820&lt;/a&gt; &amp;amp; &lt;a href="discovery-agent.js#L1855"&gt;discovery-agent.js:L1855&lt;/a&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The sample photo button in the testing drawer was hardcoded to &lt;code&gt;/experiments/kill-test/photos/p01_a.jpeg&lt;/code&gt; (a night street sign with a pitch-black background).&lt;/li&gt;
&lt;li&gt;The vision model classifies this dark background as &lt;code&gt;scene_type: 'indoor'&lt;/code&gt;, correctly triggering Case B (non-outdoor rejection with 0 DM). Testing the sample photo repeatedly gave the impression that all outdoor photos were broken.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inference Error &amp;amp; Synthetic Rejection Conflation in Popup (&lt;a href="dashboard.js#L980-L1010"&gt;dashboard.js:L980-1010&lt;/a&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Stage 7, &lt;a href="dashboard.js#L958"&gt;showDiscoveryReveal&lt;/a&gt; checked &lt;code&gt;isSynthetic = obs.eligibilityStatus === 'ineligible'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When vision inference encountered a timeout, network hiccup, or missing analysis, &lt;a href="discovery-agent.js#L301"&gt;evaluateEvidenceEligibility&lt;/a&gt; defaulted to &lt;code&gt;ineligible&lt;/code&gt;, causing technical failures to display the synthetic rejection popup ("Field Study Record" / "Physical exploration unlocks living items") instead of an error state.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Non-Deterministic Observation ID Derivation (&lt;a href="discovery-agent.js#L215"&gt;discovery-agent.js:L215&lt;/a&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IDs were generated via &lt;code&gt;obs_${Date.now()}&lt;/code&gt;. Re-uploading the exact same photo bypassed duplicate detection because the ID changed on every submission.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;h3 id="agent-session-819-1-files-changed"&gt;Files Changed&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Changes Made&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="discovery-agent.js"&gt;discovery-agent.js&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;• Isolated affirmative evidence fields (&lt;code&gt;evidence_summary&lt;/code&gt;, &lt;code&gt;visible_features&lt;/code&gt;) from &lt;code&gt;uncertainties&lt;/code&gt; during keyword scanning in &lt;a href="discovery-agent.js#L301"&gt;evaluateEvidenceEligibility&lt;/a&gt;.&amp;lt;br&amp;gt;• Added deterministic &lt;a href="discovery-agent.js#L215"&gt;generateObservationId&lt;/a&gt; using content hashing to guarantee idempotency.&amp;lt;br&amp;gt;• Broadened field-note corroboration for uncertain observations.&amp;lt;br&amp;gt;• Updated sample photo path to &lt;code&gt;p08_a.jpeg&lt;/code&gt; (genuine oak tree in field).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="dashboard.js"&gt;dashboard.js&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;• Decoupled synthetic rejections from runtime/inference errors in &lt;a href="dashboard.js#L958"&gt;showDiscoveryReveal&lt;/a&gt;.&amp;lt;br&amp;gt;• Added distinct error presentation with retry and observation-edit actions.&amp;lt;br&amp;gt;• Connected &lt;code&gt;#btn-load-sample-photo&lt;/code&gt; to &lt;code&gt;p08_a.jpeg&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="dashboard-state.js"&gt;dashboard-state.js&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;• Maintained strict verification gate preventing duplicate claims or synthetic reward leaks.&amp;lt;br&amp;gt;• Ensured reactive state updates push to &lt;code&gt;sanctuary:inventory-updated&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="styles.css"&gt;styles.css&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;• Added &lt;code&gt;.discovery-reveal-card&lt;/code&gt; entrance animation and &lt;code&gt;.sanctuary-inv-card-new&lt;/code&gt; golden glow badge styling for earned items.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h3 id="agent-session-819-1-testing-results"&gt;Testing Results&lt;/h3&gt;
&lt;h4 id="agent-session-819-1-1-genuine-outdoor-photo-p08_ajpeg--testjpeg"&gt;1. Genuine Outdoor Photo (&lt;code&gt;p08_a.jpeg&lt;/code&gt; &amp;amp; &lt;code&gt;test.jpeg&lt;/code&gt;)&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Classification:&lt;/strong&gt; &lt;code&gt;scene_type: 'outdoor'&lt;/code&gt;, &lt;code&gt;status: 'eligible'&lt;/code&gt;, &lt;code&gt;isRewardEligible: true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popup Result:&lt;/strong&gt; Displays &lt;strong&gt;🌿 A New Discovery!&lt;/strong&gt; with &lt;strong&gt;+4 Discovery Materials&lt;/strong&gt; and unlocked living items (&lt;em&gt;Ancient Oak ×2, Beaten Trail ×4, Wild Ferns ×1&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanctuary State:&lt;/strong&gt; Sanctuary backpack tray populated with 9 items with &lt;code&gt;NEW&lt;/code&gt; badges. Discovery Materials updated from 8 to &lt;strong&gt;11 DM&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="agent-session-819-1-2-digital-illustration--3d-rendering-illustrations_-16jpeg"&gt;2. Digital Illustration / 3D Rendering (&lt;code&gt;Illustrations/_ (16).jpeg&lt;/code&gt;)&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Classification:&lt;/strong&gt; &lt;code&gt;scene_type: 'digital_rendering'&lt;/code&gt;, &lt;code&gt;status: 'ineligible'&lt;/code&gt;, &lt;code&gt;isRewardEligible: false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popup Result:&lt;/strong&gt; Displays &lt;strong&gt;Field Study Record&lt;/strong&gt; with &lt;strong&gt;0 DM&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanctuary State:&lt;/strong&gt; Zero living items granted; Sanctuary backpack and Discovery Materials strictly unchanged.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="agent-session-819-1-3-duplicate-resubmission-of-claimed-photo"&gt;3. Duplicate Resubmission of Claimed Photo&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Classification:&lt;/strong&gt; Preserved with deterministic ID matching existing claimed observation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Popup Result:&lt;/strong&gt; Displays &lt;strong&gt;Discovery Saved&lt;/strong&gt; ("&lt;em&gt;Already added to your Sanctuary backpack&lt;/em&gt;").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sanctuary State:&lt;/strong&gt; Materials strictly remain at 11 DM; no duplicate items awarded.&lt;/li&gt;
&lt;/ul&gt;


&lt;h3 id="agent-session-819-1-sanctuary-persistence-across-refresh"&gt;Sanctuary Persistence Across Refresh&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verified:&lt;/strong&gt; After navigating to &lt;code&gt;#sanctuary&lt;/code&gt; and refreshing the browser, &lt;code&gt;DashboardState&lt;/code&gt; restored from &lt;code&gt;localStorage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backpack items:&lt;/strong&gt; All 9 items remained present in the inventory tray.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Materials count:&lt;/strong&gt; Remained at 11 MATS.&lt;/li&gt;
&lt;/ul&gt;


&lt;h3 id="agent-session-819-1-test-suites--build-verification"&gt;Test Suites &amp;amp; Build Verification&lt;/h3&gt;

&lt;pre class="highlight bash"&gt;&lt;code&gt;node experiments/kill-test/test_evidence_integrity.js  &lt;span class="c"&gt;# 50/50 PASSED&lt;/span&gt;
node experiments/kill-test/test_player_journey.js       &lt;span class="c"&gt;# 35/35 PASSED&lt;/span&gt;
npm run build                                          &lt;span class="c"&gt;# built in 1.08s (0 errors)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;h3 id="agent-session-819-1-remaining-limitations"&gt;Remaining Limitations&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Local Vision Inference Server:&lt;/strong&gt; Requires Ollama to be running locally (&lt;code&gt;http://&lt;span class="agent-session-redacted"&gt;[REDACTED]&lt;/span&gt;:11434&lt;/code&gt;) with &lt;code&gt;hf.co/Qwen/Qwen3-VL-2B-Instruct-GGUF:Q4_K_M&lt;/code&gt;. When Ollama is offline or times out, the app preserves the photo in the Field Journal and presents a clear retry button rather than misrepresenting it as an ineligible image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extreme Low-Light / Night Photographs:&lt;/strong&gt; Photos taken in pitch darkness without sky or horizon cues (such as &lt;code&gt;p01_a.jpeg&lt;/code&gt;) may be classified as indoor/studio by the 2B vision model. Adding a short field note (e.g. &lt;em&gt;"Night walk along the trail"&lt;/em&gt;) provides immediate corroboration to unlock full outdoor rewards.&lt;/li&gt;
&lt;/ol&gt;

                  &lt;/div&gt;
                  Show more
                &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;

  &lt;div class="agent-session-footer"&gt;
    &lt;span class="agent-session-meta"&gt;
        2 of 2 messages
    &lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Render&lt;/strong&gt;: &lt;a href="https://unseen-013z.onrender.com/" rel="noopener noreferrer"&gt;Try the UNSEEN frontend&lt;/a&gt; Render hosts the shareable frontend; the vision model still runs locally through Ollama. The deployed frontend does not, by itself, provide a hosted inference service.&lt;/p&gt;

&lt;p&gt;The AI doesn't have to know everything. It just has to help us notice something worth going outside for.&lt;/p&gt;

&lt;p&gt;And when it doesn't know, "I don't know" is an honest answer.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>I Built Third Wheel, So My Uncle Wouldn't Forget the Conversations We Shared</title>
      <dc:creator>Urooj Zahra Naqvi </dc:creator>
      <pubDate>Mon, 05 Oct 2026 06:51:48 +0000</pubDate>
      <link>https://dev.to/uroojnaqvi/i-built-third-wheel-so-my-uncle-wouldnt-forget-the-conversations-we-shared-32kp</link>
      <guid>https://dev.to/uroojnaqvi/i-built-third-wheel-so-my-uncle-wouldnt-forget-the-conversations-we-shared-32kp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;Third Wheel for my uncle&lt;/strong&gt;. One day, he asked me something about myself. The next day, he asked me the same question again. Then it happened again. And again.&lt;br&gt;
At first, I didn't think much of it. Then I noticed it wasn't a one-off. Whenever we interacted, some of the same questions would come back. And eventually, I realized what we were actually losing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The conversations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not necessarily the big, dramatic moments. The little ones. The things we had talked about yesterday. The things he had asked me. The things I had told him. The small details that make a relationship feel continuous from one conversation to the next. It made me think about my own memory too. I remember talking to someone. But do I remember exactly what they said? Do I remember what I promised? Do I remember whether something actually happened, or whether I just remember the feeling of it happening? When memory leaves a blank, our brains are very good at filling it in.&lt;/p&gt;

&lt;p&gt;So I asked myself:&lt;br&gt;
 &lt;em&gt;What if I could put the conversation somewhere before memory started changing it?&lt;/em&gt;&lt;br&gt;
That became &lt;strong&gt;Third Wheel&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Third Wheel is a private memory companion for the conversations that matter. &lt;strong&gt;Receipts, not vibes.&lt;/strong&gt; Instead of giving an AI one giant memory of everyone in your life, Third Wheel creates a separate memory space for each person. You tell it what happened. It remembers the useful parts. And when you come back later, it answers using the evidence you actually gave it.&lt;/p&gt;

&lt;p&gt;Most importantly, it separates:&lt;br&gt;
 &lt;strong&gt;What I know&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What I'm assuming&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What I don't know&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the most dangerous AI answer isn't always an obvious hallucination. Sometimes it's a &lt;strong&gt;plausible answer that was never actually said.&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frz3gq19suzhbialnn66d.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frz3gq19suzhbialnn66d.png" alt=" " width="800" height="435"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Every person gets their own private memory space instead of one shared pool of personal context.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Live Demo:&lt;/strong&gt; &lt;a href="https://thirdwheel-1.onrender.com/" rel="noopener noreferrer"&gt;https://thirdwheel-1.onrender.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo Video:&lt;/strong&gt; &lt;a href="https://youtu.be/puPd2j6azgY" rel="noopener noreferrer"&gt;https://youtu.be/puPd2j6azgY&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The project is open source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/urooj-vX/Thirdwheel" rel="noopener noreferrer"&gt;https://github.com/urooj-vX/Thirdwheel&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The core architecture is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conversation → Ingestion → Memory Extraction → Structured Memory → Evidence Retrieval → Reality Check&lt;/strong&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F802e6ocugccgo0rd7v0b.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F802e6ocugccgo0rd7v0b.png" alt=" " width="800" height="879"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Third Wheel's architecture: conversations become structured, person-scoped memory, which is later retrieved as evidence for Reality Checks.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The idea behind the architecture
&lt;/h3&gt;

&lt;p&gt;There is a problem with AI memory that I kept coming back to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Memory without provenance is just another form of hallucination.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If an AI remembers something incorrectly, that mistake doesn't disappear after one response. It can become part of the next response's evidence. So Third Wheel doesn't just store memories. It stores &lt;strong&gt;where those memories came from.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Conversation ingestion
&lt;/h3&gt;

&lt;p&gt;When I tell Third Wheel what happened, the original interaction is stored first. The AI then extracts structured information from that conversation.&lt;br&gt;
It can identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Facts, Assumptions, Uncertainties, Interests, Preferences, Interaction patterns, Events, Open threads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each extracted memory keeps a reference to the interaction that produced it. That gives Third Wheel a fundamental rule:&lt;br&gt;
 &lt;em&gt;If I can't point to the conversation that supports a memory, I shouldn't present it as a fact.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Structured memory
&lt;/h3&gt;

&lt;p&gt;I didn't want the model to freely write arbitrary information into the database. AI output is validated against structured schemas before it becomes persistent memory. This matters because a memory system can fail in a particularly dangerous way. A normal chatbot can give me a bad answer. A memory system can give me a bad answer &lt;strong&gt;today and then remember that bad answer tomorrow.&lt;/strong&gt; So the original interaction survives independently of whether AI extraction succeeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Person-scoped retrieval
&lt;/h3&gt;

&lt;p&gt;Every person gets a separate memory space. If I'm asking about Rakesh, the retrieval layer only searches the memory context belonging to Rakesh.&lt;/p&gt;

&lt;p&gt;The critical boundary is: &lt;strong&gt;user_id + person_id&lt;/strong&gt;&lt;br&gt;
This isn't just a UI convention. It's enforced throughout the server actions, repositories, and retrieval layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Reality Check
&lt;/h3&gt;

&lt;p&gt;Reality Check is the reasoning layer built on top of those memories.&lt;br&gt;
The model receives retrieved evidence and produces a structured result containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Known facts, Assumptions, Unknowns, Evidence strength, Conclusion, Closing statement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't to make the AI sound confident. The goal is to make its confidence &lt;strong&gt;earned&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Persistence
&lt;/h3&gt;

&lt;p&gt;I also wanted the system to behave like memory rather than a temporary chat response. If I run a Reality Check today and open that conversation again tomorrow, Third Wheel shouldn't silently ask the model to reinterpret everything again.&lt;br&gt;
The Reality Check is persisted with the following relevant data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Person, Interaction, Query, Evidence, Conclusion, Timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the same result can be restored without another AI call.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy2qvleybr8c8syld1n3o.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy2qvleybr8c8syld1n3o.png" alt=" " width="800" height="436"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Reality Checks are persisted, so reopening a conversation doesn't silently regenerate its history.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Voice
&lt;/h3&gt;

&lt;p&gt;For voice input, the flow is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser microphone → MediaRecorder → server → Deepgram transcription → editable transcript → existing conversation pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The audio itself isn't persisted. Once transcribed, voice becomes another way of providing the conversation that Third Wheel remembers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Was Actually Hard
&lt;/h2&gt;

&lt;p&gt;The hardest part wasn't getting an LLM to answer questions. It was getting an LLM to &lt;strong&gt;stop pretending to know things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose the conversation says:&lt;br&gt;
&lt;strong&gt;"Rakesh mentioned that he has the documents."&lt;/strong&gt;&lt;br&gt;
A conversational AI might easily turn that into:&lt;br&gt;
&lt;strong&gt;"Rakesh promised to bring you the documents."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those statements sound similar. They are not the same statement. The second one is an inference. And if Third Wheel stores that inference as a fact, the problem compounds. Next week, the AI could retrieve its own previous mistake and use it as evidence. That's why provenance became central to the architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The system needs to know not just what it remembers, but why it remembers it.&lt;/strong&gt; I also had to handle real-world model failures and rate limits. That pushed me toward a provider architecture with fallback behavior rather than coupling the entire application to one model endpoint.&lt;/p&gt;

&lt;p&gt;The final AI provider chain includes &lt;strong&gt;Gemma alongside other models&lt;/strong&gt;, while the final live demo uses &lt;strong&gt;Gemini as the primary runtime&lt;/strong&gt; because the Gemma configuration introduced too much response latency for an interactive experience.&lt;/p&gt;

&lt;p&gt;This was an important product decision: a model can be technically capable and still be the wrong choice for a live user experience if every interaction takes too long.&lt;/p&gt;

&lt;p&gt;I also wrote automated tests around the guarantees that matter most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Malformed AI output, Provider failures, Person isolation, Reality Check persistence, Historical restoration, Voice transcription&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current test suite has &lt;strong&gt;46 passing tests&lt;/strong&gt;. For a project about remembering correctly, I didn't want the important guarantees to exist only in a prompt.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4xj0rf5kesui71tmepdo.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4xj0rf5kesui71tmepdo.png" alt=" " width="800" height="704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;49 automated tests covering the parts of Third Wheel that should never be left to chance.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The biggest surprise was that &lt;strong&gt;better AI wasn't always the answer.&lt;/strong&gt;&lt;br&gt;
My first instinct was:&lt;br&gt;
&lt;em&gt;How can I make the model understand more?&lt;/em&gt;&lt;br&gt;
The more I built, the more I realized the better question was:&lt;br&gt;
&lt;strong&gt;What should the model be allowed to claim?&lt;/strong&gt;&lt;br&gt;
That changed how I approached the entire project. I started thinking less about making the AI sound intelligent and more about making its boundaries explicit.&lt;/p&gt;

&lt;p&gt;A memory can be: &lt;strong&gt;Supported,&lt;/strong&gt; &lt;strong&gt;An interpretation,&lt;/strong&gt; &lt;strong&gt;Uncertain.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those categories should never quietly collapse into one another. I also learned something less glamorous about AI infrastructure: &lt;strong&gt;failure handling is part of the product.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a preferred model endpoint is slow, rate-limited, or unavailable, a fallback can keep the application working — but it can also change latency and behavior. The user shouldn't have to care which model failed. The system needs to handle that underneath That was one of the reasons I kept the AI provider separate from the rest of the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;I don't want something as personal as my memory to be permanently coupled to one model provider. So I separated the model from the memory architecture and built a swappable provider layer that includes an open-weight &lt;strong&gt;Gemma&lt;/strong&gt; path.&lt;/p&gt;

&lt;p&gt;That makes it possible to experiment with different open models, local inference, and future fine-tuning without rebuilding the memory system itself.&lt;/p&gt;

&lt;p&gt;For something this personal, I think that distinction matters:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The memory belongs to the person using Third Wheel. The model is replaceable.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I don't want Third Wheel to become another chatbot that I have to constantly talk to. I want it to become a &lt;strong&gt;quiet personal memory layer&lt;/strong&gt;. Something I deliberately give a conversation to when I think: &lt;em&gt;I don't want to forget this.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The next things I'd explore are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local and on-device inference&lt;/strong&gt; for greater privacy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better memory correction and editing&lt;/strong&gt; when something is wrong&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit, user-controlled conversation imports&lt;/strong&gt; for people who want to preserve selected conversations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the core principle won't change:&lt;br&gt;
&lt;strong&gt;Third Wheel should never turn uncertainty into certainty.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd rather have it tell me: &lt;em&gt;"I don't have enough evidence."&lt;/em&gt; than give me an answer that sounds right but isn't true.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;I built Third Wheel iteratively with an AI coding agent. I used the agent to help implement the architecture, write tests, debug failures, build the UI, integrate the AI provider, implement persistence, and add voice input.&lt;/p&gt;

&lt;p&gt;But I didn't want the agent to simply generate a pile of code and call it done. I treated its output the same way Third Wheel treats memories: &lt;strong&gt;Useful, but not automatically true.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Generated code had to survive tests. Provider behavior had to be verified. Persistence had to be tested. Cross-person isolation had to be tested. AI failures had to be tested.&lt;/p&gt;

&lt;p&gt;That became a surprisingly fitting development philosophy for this project: &lt;strong&gt;Don't trust the output just because it sounds right. Test it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Gemma
&lt;/h3&gt;

&lt;p&gt;Third Wheel includes an open-weight &lt;strong&gt;Gemma&lt;/strong&gt; integration through its model-provider architecture. The AI layer is intentionally separated from the memory and retrieval system, allowing the application to experiment with Gemma and other models without rebuilding the core memory architecture.&lt;/p&gt;

&lt;p&gt;For the final live experience, &lt;strong&gt;Gemini is currently the primary runtime&lt;/strong&gt; because the Gemma configuration introduced too much response latency for an interactive demo.&lt;/p&gt;

&lt;p&gt;The important part is that the memory architecture remains model-independent: the model can change without changing how memories are stored, scoped, retrieved, or validated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render
&lt;/h3&gt;

&lt;p&gt;Third Wheel is deployed as a public web application on Render for the live demo.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;This is a solo submission.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Built With
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript, Next.js, React, MongoDB, Gemma, Gemini, Deepgram, Zod, MediaRecorder API, Vitest, Render&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;I started this project because of something very small. My uncle asked me the same question again. And then again. And eventually I realized that what was disappearing wasn't just an answer. &lt;strong&gt;It was the continuity between our conversations.&lt;/strong&gt; That stayed with me. Because conversations are strange things. They happen once. Then they're gone. We keep pieces of them in our heads, and sometimes those pieces change. So I built Third Wheel to keep the receipt. Not to tell me what someone meant. Not to guess what happened. Not to make a story feel complete when the evidence isn't there.&lt;/p&gt;

&lt;p&gt;Just to say:&lt;br&gt;
&lt;em&gt;This is what I remember you saying.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;This is what I can actually support.&lt;/em&gt; &lt;br&gt;
&lt;em&gt;And this is what I don't know.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because sometimes the most honest thing an AI can say is:&lt;br&gt;
&lt;strong&gt;"I don't have the receipt for that."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Third Wheel — Receipts, not vibes&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
  </channel>
</rss>
