[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-95876":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":17,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":18,"rankGlobal":10,"rankLanguage":10,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":10,"pushedAt":10,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":16,"starSnapshotCount":16,"syncStatus":27,"lastSyncTime":28,"discoverSource":29},95876,"bot-crossing","jarrenrocks\u002Fbot-crossing","jarrenrocks","A video game for AI agents.","botcrossing.com",null,"JavaScript",182,70,4,1,0,42,5.55,"MIT License",false,"main",true,[],"2026-09-21 02:04:28","# Bot Crossing — your agent threads, as a colony\n\n**[botcrossing.com](https:\u002F\u002Fbotcrossing.com)**\n\nEvery coding-agent thread on this Mac is a little astronaut. They walk out of the ship, claim\na plot for their repo, and build something. When one needs you it stops and holds a `?` over\nits head; click it and the thread opens back in whichever harness it came from.\n\nIt reads the harness's own files, on your own machine. Nothing is uploaded, there is no\naccount, and the only thing it ever writes back is a single archive flag.\n\n> **Status:** published as-is. I built this for myself and cannot promise to maintain it —\n> issues and PRs are welcome but may go unanswered, and forking is an entirely reasonable\n> thing to do. [CONTRIBUTING.md](CONTRIBUTING.md) sets out what to expect.\n\n## Run it\n\n```bash\nnpm install && npm run dev\n```\n\n`npm run dev` is the whole thing: the API lives inside the Vite dev server, so there is no\nsecond process. For a built version, `npm start` (build + serve) or `npm run serve` if\n`dist\u002F` already exists. Binds to `127.0.0.1` by default, and answers only its own page — see\n[Keeping it local](#keeping-it-local).\n\n**macOS, Linux and Windows.** Opening a thread, revealing a folder and starting a new session\nall go through a `harness:\u002F\u002F` deep link handed to the OS opener — `open(1)` on macOS,\n`xdg-open` on Linux, ShellExecute on Windows. The scanning half was portable already. Note that\nthe deep link needs a desktop app registered for that scheme, so on Linux the folder buttons\nwork while opening a thread has nothing to reach yet.\n\n## Which harnesses work\n\nA **harness** is whatever actually runs your threads. Bot Crossing reads each one's local\nsession files through a small adapter, so support is per-harness and mostly a matter of\nsomebody writing that adapter.\n\n| Harness | Status |\n| --- | --- |\n| **[Claude Code](https:\u002F\u002Fclaude.com\u002Fclaude-code)** (Anthropic) | ✅ **Supported** — desktop app and CLI, including worktrees, live-process detection and archiving |\n| [Codex CLI](https:\u002F\u002Fdevelopers.openai.com\u002Fcodex\u002Fcli) (OpenAI) | ⬜ Not yet — transcripts found at `~\u002F.codex\u002Fsessions\u002F`, [notes here](server\u002Fharnesses\u002FREADME.md#starting-points) |\n| [OpenCode](https:\u002F\u002Fopencode.ai) | ⬜ Not yet |\n| [Antigravity CLI](https:\u002F\u002Fantigravity.google) (Google) | ⬜ Not yet — the successor to Gemini CLI, which Google stopped serving individual accounts on 18 June 2026 |\n| [Cursor](https:\u002F\u002Fcursor.com) (`cursor-agent`) | ⬜ Not yet |\n| [Amp](https:\u002F\u002Fampcode.com) (Sourcegraph) | ⬜ Not yet |\n| [Aider](https:\u002F\u002Faider.chat) | ⬜ Not yet |\n| [Goose](https:\u002F\u002Fblock.github.io\u002Fgoose\u002F) (Block) | ⬜ Not yet |\n| [Qwen Code](https:\u002F\u002Fgithub.com\u002FQwenLM\u002Fqwen-code) (Alibaba) | ⬜ Not yet |\n| [Amazon Q Developer CLI](https:\u002F\u002Faws.amazon.com\u002Fq\u002Fdeveloper\u002F) | ⬜ Not yet |\n\nEvery harness that is installed shows up at once — the colony is the union of all of them, and\nan astronaut carries the name of the harness it belongs to.\n\n### Adding one\n\nOne new file in `server\u002Fharnesses\u002F`, one line in its `index.mjs`, and nothing else. The\ninterface is small and written down in full, along with the thread shape, the ground rules,\nand how to find where a given harness keeps its sessions:\n\n**→ [`server\u002Fharnesses\u002FREADME.md`](server\u002Fharnesses\u002FREADME.md)**\n\nIf you add one, a PR is very welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) first, which is\nhonest about how much support I can offer. If landing it means editing the scanner or anything\nunder `src\u002F`, please mention that: it means the seam needs widening, and I would rather fix that\nthan have you work around it.\n\n## What you are looking at\n\n| In the colony | In your threads |\n| --- | --- |\n| One hex zone | One repo. Bigger repos claim more tiles — one per seven threads, grown as a contiguous blob from the middle outward. A zone stays where it is: see below |\n| One astronaut + one building | One session |\n| How finished a building looks | How large its transcript is, on a log scale |\n| Scaffolding | Somebody is at that site right now |\n| Walking out of the ship | A thread that just appeared |\n| Walking back into the ship | You archived it |\n\n### A zone stays where it is\n\nThe map is only useful if you can learn it, so the layout is *sticky*. The previous\narrangement is an input to the next one: a repo that still needs the same number of tiles\nkeeps exactly the tiles it had, one that grew keeps them and claims neighbours, and one that\nshrank gives back whatever it claimed most recently — so growing and shrinking again returns\na zone to precisely the shape it started in. Only a repo that has never been placed is\nplaced at all, and it takes the innermost tiles still free.\n\nA zone's origin is its **root** tile rather than the centre of the tiles it happens to hold,\nso gaining one does not drag its buildings, its crew and its name sideways; the new tile\nsimply appears alongside. And the arrangement is written to `data\u002Fcolony.json`, so the map\nyou have learned survives a reload — including for a repo whose last thread you archived,\nwhich comes back to the same ground when you start a new one.\n\nThe version before this was a pure function of the thread counts: one session appearing\nanywhere changed the sort order, the order decided the tiles, and the whole colony re-laid\nitself out. A zone you were watching could jump to the far side of the map because a\n*different* repo gained a thread.\n\n### The deck has to clear the ground\n\nInside the colony the terrain is gentle but not flat — it runs from about -0.3 to +0.24 on\nthe Moon, and half again as far on Mars. A deck's top face has to sit above the roughest\nground any plot can be dealt, or the ground comes through it: the slab reads as sunken, props\nstanding on it are buried to the waist, and every surface where the two meet tears. So the\nslab is 0.45 tall, and everything on a plot — buildings, kerbs, clutter, boots — is measured\nfrom that one number rather than from a height of its own.\n\nGround scatter has to miss the plots, and the order it happens in is the whole problem: the\nworld is built before the first roster arrives, so at that moment there are no plots to miss.\nBoulders and trees would end up under decks laid on top of them afterwards, poking through in\nfragments. The scatter is therefore rebuilt whenever a zone's footprint changes — cheap,\nbecause it no longer drags the terrain mesh along with it.\n\nAstronaut behaviour is a **strict precedence** rather than a set of independent flags, so a\nthread can only ever be doing one thing. First match wins:\n\n| Signal | What the astronaut does | Badge |\n| --- | --- | --- |\n| Errored | Slumps, red eyes, fault light stutters | `!` |\n| Running now | Hammers away at its building, sparks fly | `⚒` |\n| PR merged | Jumps, confetti, heart eyes | `✓` |\n| Unread | **Stops and waits on you** | `?` |\n| Nothing for 3 days | Sits down and sleeps, `z` bubbles | — |\n| Anything else | Potters around its plot | — |\n\nOnly the states that want something from you get a badge. With most of a real thread list\nsitting quiet, a symbol over every astronaut buries the one `?` that actually matters.\n\nZone names follow the same rule: a plot shows its name only while somebody there is working,\nwaiting or stuck. Everything else is nameless until you point at it. The plate itself is just\ntext over a soft halo with a small accent dot — no panel, no outline.\n\n## Getting about\n\nAstronauts route rather than drift. Buildings and the landing pad are rasterised into a\nnavigation grid whenever the roster changes, and the crew walks it with A*, string-pulled\nafterwards so they take the corners they actually need instead of a visible staircase.\n\nTwo guarantees, deliberately independent:\n\n- **Routing** finds a way *around* a building, including threading the gaps between a ring of\n  them. Blocking radii are the building's bounding radius trimmed a little plus the\n  astronaut's own width — the trim is what keeps those gaps walkable.\n- **Collision** is applied to every step whether or not a path is being followed. Routing can\n  fail — a site walled in between polls, a path budget that has not caught up — and walking\n  through a wall must not be what happens when it does. Blocked head-on, an astronaut slides\n  along the obstacle instead of stopping dead.\n\nMeasured over the live colony: 288 path legs, **0 crossing a building**, and **0 penetrations\nacross 78,000 agent-frames**. A typical path costs 6 µs (most are a clear straight shot and\nskip the search); the worst frame when a poll invalidates every route at once is 0.6 ms.\n\nThey also push each other apart, so a busy plot is a crowd rather than a pile. That spacing\nis measured against the widest thing an astronaut wears — the helmet, at 0.95 units — because\nholding a crowd at less than that is a crowd standing *inside* itself, which is what the first\nversion did at 0.72. Arrival is derived from the same number and is deliberately larger: an\nastronaut that had to get closer than its neighbours would let it could never finish arriving,\nand would shoulder at the crowd for as long as its thread existed.\n\nStanding spots are placed clear of the building's own blocked radius rather than at a fixed\ndistance from it, and checked against the navigation grid — a spot inside a wall is a spot the\ncrew can never reach, and the astronaut sent to it walks at that wall forever. Measured over\nthe live colony: **68 of 68 astronauts settled, nobody closer than 1.14 units, no standing spot\nleft inside an obstacle.** As a last resort an astronaut that has been blocked for six seconds\nadopts the ground it got to instead of pushing on.\n\n## Clicking one\n\nAll of the chrome is one panel on the right — the name, the counts, and every repo. There\nis no top bar and no strip along the bottom: a colony is a place, and a place reads better\nwithout a frame around it.\n\nAn astronaut, a zone's deck, the name plate over it, or a repo in that list — all four drill\ninto the same repo. Picking somebody is also picking the zone they are standing on.\n\n**The repo**, at the top, whether or not anybody is selected:\n\n- **New conversation** (`C`) starts a fresh thread in that folder. It is the same\n  `claude:\u002F\u002Fcode\u002Fnew?folder=…` deep link Finder's \"New Claude Code Session Here\" quick\n  action uses, so the desktop app opens an empty session with the repo as its workspace —\n  nothing is resumed and nothing is written.\n- **Finder** (Explorer on Windows) opens the folder, **Copy path** copies it.\n- Underneath, everything running in that repo, whoever wants something first. Clicking one\n  flies to its astronaut and selects it.\n\n**The thread**, when an astronaut is selected, in a card parked **beside that astronaut**\nrather than in the panel: its face, title, worktree, branch, model, last activity, and how\nfar along its building is. The answer to \"what is this one doing\" belongs next to the thing\nyou clicked, so the card follows its astronaut around the screen — preferring its right,\nflipping to its left rather than sliding under the sidebar, and never leaving the window.\nIt is moved with a transform rather than with `left`\u002F`top`, the one geometric change a\nbrowser makes without touching layout, so following a walking astronaut costs nothing.\n\n- **Open** hands the thread back to Claude Code and the desktop app comes forward.\n- **Archive** sets `isArchived` on Claude Code's own session record — the thread lands in\n  Claude Code's Archived list, not just here — and the astronaut walks back up the ramp and\n  boards the ship.\n\nOnly one button in the panel is ever the accent colour: whichever action is the immediate\none. `Esc` steps outward a notch at a time — the thread first, then its zone.\n\nOpening uses `claude:\u002F\u002Fclaude.ai\u002Fepitaxy\u002F\u003Clocal_…>`, which *navigates* the desktop app to a\nthread it already has. `claude:\u002F\u002Fresume` is the fallback for threads that only exist as a CLI\ntranscript: it *imports* the transcript, which creates a second untitled session and rewrites\nthe `.jsonl`, so it is only ever used when there is nothing to navigate to.\n\nArchiving carries a deliberate one-writer discipline: the browser owns\n`data\u002Fcolony.json` and PUTs it whole, `\u002Fapi\u002Farchive` only touches Claude Code's records. If\nboth wrote it, a save from a page holding older state would silently drop every archive made\nsince that page loaded. Claude Code also rewrites its session records from memory and can\nstomp the flag, so the colony re-asserts it on every scan — an archive that gets stomped comes\nback within one poll.\n\nNothing is ever written to your Claude Code data except that one `isArchived` field. The\nfolder buttons only ever hand a path to `open`.\n\nThe deep links above are the **Claude Code adapter's** business, not the colony's — another\nharness plugs its own in, and a harness with no deep link simply greys the button out. See\n[`server\u002Fharnesses\u002FREADME.md`](server\u002Fharnesses\u002FREADME.md).\n\nPlots are keyed by the folder's *name*, which is all the colony needs to draw one, so the path\nis read back off the threads standing there. Where a name is ambiguous — `~\u002Fworkspaces\u002F1\u002Ffoo`\nand `~\u002Fworkspaces\u002F2\u002Ffoo`, which is what you get keeping parallel copies instead of worktrees —\nit grows leftward until it is not, and you get `1\u002Ffoo` and `2\u002Ffoo` on separate ground. Only\nnames that actually collide change, because the name is also the key your saved layout is\nstored under and disambiguating everything would move every plot on the map. A repo that has\nmoved or gone since the last scan fails at the server rather than handing `open` a dead path.\n\nName plates are hit-tested in screen space rather than raycast: they are billboarded in the\nvertex shader, so a raycast would test the quad where it was authored rather than where it\nended up. That test deliberately ignores whether the plate is currently faded in — pointing\nat where a quiet project's name *would* be is exactly what makes it appear.\n\n## Getting around\n\nNavigation is Google Earth's, including both of the things that make Earth feel like Earth:\n\n| | |\n| --- | --- |\n| **Drag** | Grabs the ground. The point under your cursor stays pinned there for the whole drag |\n| **Right-drag** (or ⌃ \u002F ⇧ \u002F middle-drag) | Tilt and rotate. Up tilts toward the horizon |\n| **Scroll** | Zooms **at the cursor**, not at the screen centre |\n| **Two fingers** | Pinch to zoom, drag to pan — both anchored between your fingers |\n| **Arrows**, **+** \u002F **−** | Move and zoom from the keyboard |\n| **Orbit mode** (rail button, or `O`) | Earth's auto-rotate: a slow sweep around whatever is centred, about two minutes a revolution. It drives the heading only, so you can keep dragging, tilting and zooming while it runs |\n\nBoth anchors are exact, not approximate: a 200px drag holds its grabbed point to 0.00 world\nunits, and dollying 62→37 holds the cursor's point to 0.01.\n\nOptionally, letting go can ease the *angle* back to the nearest clean isometric heading after\na couple of seconds. Your position and zoom are never touched — going home on its own would\nfight you; tidying the angle after you stop does not. It is off by default, because a camera\nthat moves when you did not ask it to is startling the first time you meet it. Turn it on\nunder **View → Return to isometric**.\n\n## Keys\n\n| Key | Does |\n| --- | --- |\n| `H` \u002F `⌘\\` | **Hide every panel.** The colony still reads: status lives above the astronauts' heads |\n| `S` | Settings |\n| `N` | Fly to the next astronaut waiting on you |\n| `Enter` \u002F `A` | Open \u002F archive the selected thread |\n| `C` | New conversation in the open zone's folder |\n| `O` | Orbit mode |\n| `Tab` | Next planet |\n| `L` | Next time of day |\n| `P` | Screenshot |\n| `0` | Reset the view |\n| `Esc` | Deselect, and close the zone sidebar |\n| `?` | Help |\n\n## Planets and light\n\nThree worlds — **Luna**, **Mars**, **Terra** — and a full day\u002Fnight cycle you can scrub or\nlet run. A planet is a bag of colours and two switches; terrain, scatter, sky and lighting all\nread from the same preset, so a fourth world is a data change rather than a code change.\n\n### The sky is the HDRI\n\nRather than shipping an HDR environment map, the sky shader **is** the environment map. A\nsecond copy of the sky dome — sharing the same uniforms, so it is always the sky you are\nactually standing under — is rendered into a prefiltered radiance map with `PMREMGenerator`\nand bound as `scene.environment`. That is what gives metal something to reflect and\ndielectrics a directional ambient, and it is why the colony changes *character* through the\nday rather than just changing brightness: at dusk on Mars the panels pick up the sky, on the\nMoon they stay hard and neutral.\n\nIt regenerates only when the sky has actually moved, and never more than a few times a second.\nMeasured cost: **0.16 ms\u002Fframe**. Off on Potato and Low; the intensity is a slider.\n\nMaterials are properly PBR underneath it. Roughness and metalness are looked up per atlas\ncell, so a single merged building geometry holds painted panel, brushed metal and\nphotovoltaic glass and each behaves correctly — the ten building recipes never had to learn\nabout PBR.\n\n### The sun is not overhead\n\nThe solar arc is tilted, so noon puts the sun 54° above the horizon and off to one side rather\nthan at the zenith. That is load-bearing rather than decorative: a sun directly overhead puts\n`N·L` at zero on every vertical wall in the colony, and they go black with only ambient to\ncatch. The old procedural buildings were curved enough to hide it; a kit of flat-walled\nmodules is not.\n\n### HDR and bloom\n\nEye colours, lamps, windows, crop rows and plot kerbs are all authored above 1.0 so the bloom\npass picks them out. The threshold is deliberately high (0.92) — only those things clear it,\nso lit surfaces stay crisp instead of going hazy.\n\n## Where the art comes from\n\nThe colony is built out of two CC0 asset packs by **[Kay Lousberg](https:\u002F\u002Fkaylousberg.com)**,\nplus the project's own shaders on top of them.\n\n| Pack | Used for | Licence |\n| --- | --- | --- |\n| [KayKit : Space Base Bits](https:\u002F\u002Fkaylousberg.itch.io\u002Fspace-base-bits) | Every building, the landing pads, rovers, and the crates and drums stacked around each plot | CC0 |\n| [KayKit : Character Animations](https:\u002F\u002Fkaylousberg.itch.io\u002Fkaykit-character-animations) | The crew's body and all fifteen animation clips they play | CC0 |\n| [KayKit : Forest Nature Pack](https:\u002F\u002Fkaylousberg.itch.io\u002Fkaykit-forest) | Terra's trees, bushes and grass, and the boulders on every world | CC0 |\n\nCC0 asks for nothing, but crediting Kay costs nothing either. If you rebuild the assets, both\npacks go in `assets-src\u002F` (see below).\n\nTwo things about Space Base Bits make the whole approach work. It is **modular** — a habitat is\na base module with a roof module on it, a workshop is the garage variant with a rover parked\noutside — which is why ten building recipes fit on one screen. And all forty-four models share\n**one 1024px gradient atlas**, so a nine-part greenhouse still merges to a single geometry and a\nsingle draw call, exactly as the procedural generators it replaced did.\n\nThat atlas is an 8×4 grid of swatches, which turns out to be a useful thing to have. A *cell\nindex* is a stable name for a material, so the building shader can:\n\n- **repaint one swatch into the repo's accent.** Kay's gold trim band is cell 11; the fragment\n  stage swaps its hue while keeping the swatch's own light-to-dark gradient, so every plot's\n  buildings wear that plot's colour with no extra material and no extra draw.\n- **light that same swatch after dark**, which is what makes the window strips come on at night.\n- **give one flat texture real PBR.** Roughness and metalness are looked up per cell, so the\n  grey structural swatch behaves like painted metal and the photovoltaic swatch like glass.\n\nThe Forest pack does double duty. Its boulders are painted neutral grey, which means a\nper-instance tint takes exactly the same rock to lunar dust or Martian rust without touching\nthe atlas — so one scatter recipe dresses a meadow and a crater field. Only sixteen of its 105\nmodels are packed: variety comes from per-instance scale and rotation, and packing every size\nand colour variant would be five times the file for no more to look at.\n\n### The surfaces are drawn, not shipped\n\nThe plot decks and their kerbs can't be textures from a pack, because they have to take each\nrepo's accent colour and a painted texture cannot. `world\u002Fsurfaces.js` draws them to a canvas\nat boot instead — a plated metal floor of bolted panels, and a kerb broken into dashes that\nreads as runway edge lighting rather than a glowing bar. Both are authored neutral grey so the\nmaterial's colour multiplies through cleanly, and both come with a **normal map derived from\ntheir own height field** by Sobel. That relief is doing most of the work: on a surface this\nlarge and this flat, a flat albedo pattern under one directional light reads as wallpaper,\nwhere a seam that catches a shadow along one edge and a highlight along the other reads as\nmetal.\n\nBoth surfaces needed their UVs rebuilt, and both for the same underlying reason: a generated\nprimitive's unwrap is made for the primitive, not for what you draw on it.\n\nA hex tile is a six-sided cylinder, and a cylinder's cap UVs are a *disc* — which turns a tiling\nplate pattern into a medallion, one per tile. The deck's **top** is therefore reprojected from\nworld XZ, so the seams run straight across a whole plot and seven cells read as one apron. Its\n**rim** keeps the cylinder's own side unwrap, which is the one thing that works: a fixed\nhorizontal axis like `x + z` is *constant* along two of every six sides, leaving those faces\nwith no UV gradient, a degenerate tangent and — since three builds the normal-mapped shading\nframe out of that — solid black; and arc length from `atan2` fixes the gradient but adds a seam\nwhere the wrap crushes a dozen repeats into one panel. The generated unwrap has neither problem,\nbecause it duplicates the vertices at the seam.\n\nA kerb bar is a box, and a box hands all six faces the same 0..1 square, so the dash strip was\nstretched down the sides and across the ends as well — which on a bar 14cm tall squashed the\ndark gaps between dashes into what read as a solid black edge, worst where six of them gather at\na plot corner. Only the upper face points at the strip now; the rest point at a patch of flat\ncolour on the same texture.\n\n### Rebuilding them\n\n`npm run assets` packs the raw packs into the two glbs the app loads. The built files are\nchecked in and the raw packs are not, so this is a no-op unless you have fetched them:\n\n```bash\nmkdir -p assets-src && cd assets-src\n# download the FREE tier of both packs from the links above, then unzip in place\n```\n\n`npm run assets` runs `tools\u002Fbuild-assets.mjs`, which drives `build-kit.mjs` once per model\npack — merging a directory of single-model `.gltf` files into one document with one material\nand one texture — and then `build-crew.mjs`. That last one keeps the fifteen clips the colony actually plays out of\nKayKit's 161 and — the part that matters — **retargets every animation channel onto the\nmannequin's own bones**. Merging glTF documents brings each animation file's private copy of the\nrig along with it, so without that step the finished file has five skeletons named `hips` and\nthe clips drive the four nobody is looking at. It loads without a single warning and renders the\nentire crew frozen in its bind pose.\n\n## Animating the crew\n\nThe bodies are hand-animated clips, and hand-animated clips are not instanceable: three skins a\n`SkinnedMesh` from a `Skeleton` object, one per character, which for three hundred threads means\nthree hundred draw calls and three hundred skeletons stepped on the CPU every frame.\n\nSo the animation is **baked once, at load, into a bone-matrix texture**. Every clip is sampled at\n30 fps and each frame's twenty-one skinning matrices are written into a float texture — 84×723\ntexels for the whole set. One `InstancedMesh` then carries the entire crew, and each astronaut\nreads its own row of that texture from a single per-instance float: the frame it is on. Skinning\nhappens in the vertex shader, upstream of three's own instancing, so the skinned vertex still\ngoes through `instanceMatrix` and the crew stays one draw whether there are six of them or six\nhundred.\n\nEverything the crew *wears* stays procedural and stays the colony's own: helmet, visor,\nscreen-face, backpack, antenna and lamp. Those are pinned to bones the cheap way — the bake also\nwrites the head and chest world transforms into a small array on the CPU, so placing a helmet is\none matrix read rather than a skeleton evaluation, and a helmet can never be a frame out of step\nwith the head under it.\n\nBehaviour maps onto clips directly, and locomotion wins over status — an idler pottering across\nits plot walks rather than hammering while it slides:\n\n| Behaviour | Clip |\n| --- | --- |\n| Running now | `Hammering` |\n| Waiting on you | `Waving` |\n| Errored | `Hit_A` |\n| PR merged | `Cheering` |\n| Nothing for three days | `Sit_Floor_Down` → `Sit_Floor_Idle`, and then it holds still |\n| Anything else | `Idle_A`, or `Walking_A` \u002F `Running_A` while moving |\n\nThe clip is chosen from the distance an astronaut **actually covered** last frame, not from\nthe velocity it meant to have. The two come apart the moment something is in the way:\ncollision refuses the step while velocity stays high, and an agent driven off intent alone\nwalks on the spot against a wall. The measure rises instantly and falls over a tenth of a\nsecond — so setting off is caught on the frame it happens and nothing ever slides in a\nstanding pose, while a stride still gets to finish instead of freezing mid-step.\n\nMovement is shaped to match. A wander leg is walked at a decisive pace and stops dead on\narrival rather than easing down through the speeds no standing clip can carry, and a leg that\nruns into the side of a building is abandoned at the first refused step. Measured across a\nlive colony over a minute: **0.4% of agent-frames** disagree with what the body is doing, none\nof them by more than 0.12 m\u002Fs.\n\nStride playback follows actual ground speed, so short steps cannot moonwalk. An *idler*\npotters around its plot; a *sleeper* does not — it sits where it sat, and the only thing that\ncan move it is being pushed out of someone it is overlapping, which converges and stops. The\nalternative is a cross-legged astronaut sliding across the deck, standing up to walk two\nmetres, and sitting down again every few seconds.\n\nClips that do not loop are baked a millisecond short of their own duration. Sampled at exactly\n`duration` the mixer's default loop mode wraps to the start, so the frame a sit-down or a spawn\n*holds* would be the pose it began from — and the astronaut snaps upright on the last frame of\nsitting down.\n\nThe crew also stands on the ground rather than on `y = 0`. A plot's tiles are a raised slab\nand the terrain between plots rolls half a metre either way, so a fixed height buries them for\na good part of the colony. `Colony.groundAt()` answers with the deck height when a point is\nover an allocated hex cell — an exact axial lookup, not a nearest-centre radius test — and the\nterrain field otherwise. It is sampled only when an astronaut has actually moved, and eased\ninto, so walking up onto a deck reads as a step rather than a teleport.\n\n## Performance\n\nFive presets from **Potato** to **Ultra**, and every knob underneath them is individually\nadjustable. A dot next to a setting means you have moved it away from its preset.\n\nThe knobs that actually matter, and why:\n\n- **Render scale** is the biggest lever there is. The drawing buffer is sized directly rather\n  than through `setPixelRatio`, which cannot usefully go below 1 on a retina panel. It is a\n  share of *your display's own resolution*, so 100% is native on a retina panel and native on\n  a 1× one. Reading it as CSS pixels — which is what it used to do — quietly rendered every\n  retina machine at half resolution, and the first place that shows is the small stuff that\n  holds a constant size on screen: the badge glyphs and the zone name plates, which magnify\n  hardest exactly when you lean in to read them.\n- **Adaptive quality** watches the frame time and quietly scales *under* whatever you chose,\n  one step per second — a governor that reacts per frame makes the resolution visibly breathe.\n  Its floor is relative too: half of what your display can show, not half a CSS pixel.\n- **HDR + bloom** off doesn't just skip the pass, it disposes the composer's float render\n  targets. Turning it off on a weak machine gives the memory back.\n- **Shadows** track the camera rather than covering the whole colony, which is worth roughly a\n  doubling of effective resolution.\n\nWhat keeps it cheap at rest:\n\n- The crew's animated bodies are a single instanced, GPU-skinned draw, and each worn part —\n  helmet, visor, face, pack, antenna, lamp — is one `InstancedMesh` across the whole crew. The\n  sixty-fifth astronaut costs a matrix write and one float, not a draw call. Per-agent suit\n  colour, eye colour and facial expression ride along as instanced attributes.\n  Measured on a live colony: **66 astronauts and 66 buildings in 105 draw calls**.\n- Each building merges into a single geometry, and construction progress is a shader offset\n  rather than a rebuild, so a building rises out of the ground without touching a vertex\n  buffer. It sinks the structure and discards what falls below the deck rather than slicing\n  the top off, so a half-built one is a *whole* building partly buried — cutting instead\n  guts a kit of closed shells, and a two-thirds-finished biodome becomes an empty ring.\n- Terrain is displaced and vertex-coloured once at build time; the GPU only ever sees static\n  geometry.\n- Particles live in flat typed arrays and are swap-removed on death — no allocation during play.\n\n### Things that hold their size on screen\n\nBadges and name plates are deliberately near-constant on screen, which inverts the usual\ntexture problem: they are *minified* when you pull the camera out and *magnified* when you\nlean in, and the close end is the one that hurts. Both are sized for the closest you can\nget — the badge atlas gives each glyph a 128×256 cell, a plate is drawn at 4× — so at the\ntightest zoom on a retina panel there is still about one texel per device pixel, and mipmaps\nplus anisotropy carry the far end where a plate is sixty pixels tall and would otherwise\ncrawl. Everything in both is drawn from paths, so the only cost of more texels is memory.\n\n## The faces\n\nEach visor is a little rounded screen — the patch is a rectangle in UV space, so its rounded\nsilhouette is cut in the fragment shader with a rounded-box SDF, which gives soft corners a\nrectangular patch can never have and lets the white helmet show through where the screen ends. All sixteen expressions are drawn once into a single 4×4\ncanvas atlas as a white-on-black **mask** — never as finished artwork — and the colour arrives\nper-astronaut at draw time, so one 512px texture gives every agent its own eye colour without\na second byte of memory. The shader reads the mask out of the red channel, blends between the\ndark screen and that astronaut's glow, and adds scanlines and a vignette so it reads as a\nscreen rather than a decal.\n\nThey blink on their own clocks, so a crowd never blinks in unison.\n\n## A note on which side gets drawn\n\nThe ship is procedural, and its bowls, engine bells and airlock collars are **open shells**.\nTwo things bite there: single-sided rendering lets you look straight through them, and a\none-sided bowl cannot shadow-map — from the sun its concave interior is a back face at exactly\nits own depth, so it self-shadows to solid black whichever cull mode the depth pass uses. So\nthe ship draws double-sided with a `BackSide` shadow side.\n\nThe buildings want the exact opposite, and for the exact opposite reason. The model kit's\npieces are **closed solids**, so there is nothing to see through — and being closed is why they\nmust not be drawn double-sided. They are modelled as stacked boxes, which leaves a floor and\nthe ceiling underneath it sharing a plane all over the kit: a landing pad and the lander\nstanding on it put 38 up-facing and 17 down-facing triangles at one height, and a habitat has\ntwo such planes, a lab four. Drawn double-sided, both halves of every one of those pairs\nrasterise at identical depth and the winner is settled by floating-point noise — which is a\nwhole colony of surfaces flickering as the camera moves. Back-face culling throws the downward\nhalf away before it can fight, so buildings render `FrontSide`.\n\nWorth knowing if you add a kit: the tell is that *every* clash is an up\u002Fdown pair. Not one is\nup\u002Fup, which is what makes culling a complete fix rather than a partial one.\n\n## Turning things\n\nTurbine rotors spin in the **vertex shader**, not as child meshes, so a turbine is still one\nmerged geometry and one draw call. Each spinning vertex carries the hub it turns about and how\nfast, which is what lets one building hold several of them, and one uniform write a frame turns\nevery rotor in the colony. The tower is taken from the kit *solo* — without the sub-node the\npack names separately — precisely so the rotor can be put back on as a part that moves.\n\nTwo things to watch if you add another: `BufferGeometry.scale()` transforms position and normal\nand nothing else, so an attribute that holds a *position* has to be scaled by hand or the blades\norbit a hub left behind at the unscaled height. And the shadow pass needs the same rotation, or\nthe blade's shadow lags the blade.\n\n## Keeping it local\n\nThe server reads your agent transcripts and can ask the OS to open things, which makes it a\nmore interesting target than a localhost toy usually is. Three things hold it in:\n\n- **It binds `127.0.0.1`.** Nothing outside the machine can reach it, unless you deliberately\n  change that — see below.\n- **It checks `Host`.** Binding to loopback is not on its own enough. An attacker who points\n  a domain they control at `127.0.0.1` — DNS rebinding — reaches the server *as a same-origin\n  page* and can then read every reply. Those requests still arrive carrying\n  `Host: their-domain`, and are refused.\n- **It checks `Origin`.** A cross-site `fetch` with a `text\u002Fplain` body is not preflighted, so\n  without this any page you happened to have open could POST here — spawning sessions, opening\n  Finder windows, or overwriting the colony layout — even while unable to read the response.\n  Requests from anywhere but this server's own page are refused.\n\nThe practical cost: a bare `curl` POST is refused too, since browsers always send `Origin` on\nPOST and its absence means the caller is not the page. Add `-H 'Origin: http:\u002F\u002Flocalhost:5274'`\nif you are scripting against the API.\n\n### Serving it to your network\n\n`BOT_CROSSING_HOST` changes what `npm run serve` binds to, so you can watch the colony from a\ntablet on the sofa:\n\n```bash\nBOT_CROSSING_HOST=0.0.0.0 npm start\n```\n\n**Understand what that hands out before you do it.** The two checks above stop a *web page* from\ndriving the server; they are not access control, and they do nothing about another device asking\ndirectly. Anyone who can reach the port gets every thread title, every opening prompt, every\nworking directory and branch — a fairly complete picture of what you have been working on — plus\nthe ability to open threads, reveal folders and start sessions on your machine. There is no\npassword, because there was never meant to be anything to guard.\n\nFine on a network you own. Not something to leave running on café wifi, and worth remembering\nthat a machine on a VPN or a mesh network is reachable by everything else on it too.\n\nWhat it touches on disk, in full:\n\n| | |\n| --- | --- |\n| Reads | Your harness's own session records and transcripts |\n| Writes | `data\u002Fcolony.json`, and **one** `isArchived` field per archived thread |\n| Sends | Nothing. No network calls, no telemetry, no account |\n\n`data\u002Fcolony.json` holds the names and paths of the repos you work in, so it is gitignored —\nworth knowing before you copy one into an issue.\n\n## Layout\n\n```\nserver\u002F\n  harnesses\u002F   one adapter per agent harness — README.md is the contract\n    index.mjs    the registry: add your harness to the list here\n    claude-code.mjs\n  lib\u002F         filesystem helpers the adapters share\n  scan.mjs     harness-agnostic: asks every detected harness, merges, sorts\n  api.mjs      \u002Fapi\u002Fthreads, \u002Fapi\u002Fharnesses, \u002Fapi\u002Fstate, \u002Fapi\u002Fopen, \u002Fapi\u002Farchive,\n               \u002Fapi\u002Fnew-session, \u002Fapi\u002Freveal\n  serve.mjs    static server for the built app\nsrc\u002F\n  core\u002F        settings, renderer + post chain, the Google Earth camera\n  world\u002F       planets, terrain, sky, hex plots, the model kit, buildings, the ship\n  agents\u002F      the crew rig and its bake, instanced astronauts, faces, badges, particles\n  game\u002F        threads → colony, and the API client\n  ui\u002F          the HUD\ntools\u002F         asset packers — raw packs in, the three glbs the app loads out\npublic\u002Fassets\u002F spacebase.glb, crew.glb, forest.glb\n```\n\nEverything that knows what a *particular* harness's files look like lives in\n`server\u002Fharnesses\u002F`. Everything else — the scanner, the API, the whole of `src\u002F` — is written\nagainst the thread shape and never against a harness.\n\nColony state lives in `data\u002Fcolony.json` — where each zone sits and what you archived.\nDeleting it only loses the archive list and the map's arrangement; the threads themselves are\nuntouched, and the colony lays itself out again from scratch.\n\n## Building your own\n\nBot Crossing is one shape this idea can take. `.claude\u002Fskills\u002Fagent-session-world\u002F` is a skill for\nbuilding the others — fish in a reef, animals in a forest, villagers, ants, boats in a harbour.\nWhatever inhabits it, the structure underneath is the same: a layout that stays put so you can\nlearn the map, one draw call for the whole crowd, a single source of truth for what a thread is\ndoing, and a camera with weight.\n\nIt is written to take somebody's idea and fill in the frame around it, rather than to reproduce\nthis particular colony. Four reference files carry the detail, and stand on their own whether or\nnot you build anything like this:\n\n- [making it feel alive](.claude\u002Fskills\u002Fagent-session-world\u002Freferences\u002Fmaking-it-feel-alive.md) —\n  ambience and interaction, written to translate into any metaphor\n- [rendering traps](.claude\u002Fskills\u002Fagent-session-world\u002Freferences\u002Frendering-traps.md) — the\n  graphics problems in roughly the order you meet them\n- [harness adapters](.claude\u002Fskills\u002Fagent-session-world\u002Freferences\u002Fharness-adapters.md) — reading a\n  coding agent's sessions without disturbing them\n- [asset pipeline](.claude\u002Fskills\u002Fagent-session-world\u002Freferences\u002Fasset-pipeline.md) — decent art\n  without an artist\n\n## Who made this\n\nBuilt by **[Jarren Rocks](https:\u002F\u002Fjarren.rocks)**, mostly as a side effect of building\n**[Emra](https:\u002F\u002Femra.app)** — which is where most of the threads in the screenshots come from,\nand why a tool for keeping track of a lot of them at once existed in the first place.\n\n## Licence\n\n[MIT](LICENSE) © Jarren Rocks. Do what you like with it — including forking it, which\n[CONTRIBUTING.md](CONTRIBUTING.md) explains is a first-class option rather than a last resort.\n\nThe art is not mine. Three CC0 packs by **[Kay Lousberg](https:\u002F\u002Fkaylousberg.com)** — [Space\nBase Bits](https:\u002F\u002Fkaylousberg.itch.io\u002Fspace-base-bits), [Character\nAnimations](https:\u002F\u002Fkaylousberg.itch.io\u002Fkaykit-character-animations) and [Forest Nature\nPack](https:\u002F\u002Fkaylousberg.itch.io\u002Fkaykit-forest) — are built into the `.glb` files in\n`public\u002Fassets\u002F` and are covered by [CC0](https:\u002F\u002Fcreativecommons.org\u002Fpublicdomain\u002Fzero\u002F1.0\u002F),\nnot by the MIT licence above. CC0 asks for nothing; crediting Kay costs nothing either.\n\nThe status badges above each astronaut's head are\n[Material Design Icons](https:\u002F\u002Fpictogrammers.com\u002Flibrary\u002Fmdi\u002F), bundled via `@mdi\u002Fjs` and\nlicensed [Apache-2.0](https:\u002F\u002Fgithub.com\u002FTemplarian\u002FMaterialDesign\u002Fblob\u002Fmaster\u002FLICENSE).\n\nEverything else you see — the shaders, the terrain, the sky, the ship, the crew's helmets and\nfaces, the plot decks and their kerbs — is drawn by this project and is MIT along with the code.\n\nNot affiliated with Anthropic, OpenAI, Google, or any of the other harness vendors listed above.\n","Bot Crossing 是一款面向本地 AI 编程代理（coding agents）的桌面可视化管理工具，将各类 AI 代码助手（如 Claude Code）的运行会话抽象为可交互的‘宇航员’角色，在图形界面中以殖民地形式统一呈现。核心功能包括：通过 deep link（harness:\u002F\u002F）与本地 AI 工具深度集成、实时扫描并映射各 harness 的本地会话文件、零上传隐私设计（所有数据保留在用户设备）、支持多 harness 并存与跨工具上下文识别。适用于开发者在本地同时使用多个 AI 编程代理时，进行会话发现、状态监控与快速跳转调试的轻量级协同场景。",2,"2026-09-05 02:30:06","CREATED_QUERY"]