Back to Outbreak: Soycoon City
SOURCE / PINNED RELEASE

Made of little things.

Outbreak: Soycoon City

Release
db7750b1d009…
Author-recorded commit
3ee3f5fd27a6…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b87c04571a9

Archive hash verified: dbebdc772b142a1c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/connections.ts
import { doorIssue, doorOffset, doorPosition, type Door, type Point, type Room, type Stage } from './world.js';
export type Endpoint = { room: Room; side: Door['side']; offset: number };
let serial = 0;
export function wallEndpoint(room: Room, local: Point): Endpoint {
  const sides: [Door['side'], number][] = [['west', Math.abs(local.x + room.w / 2)], ['east', Math.abs(local.x - room.w / 2)], ['north', Math.abs(local.z + room.d / 2)], ['south', Math.abs(local.z - room.d / 2)]];
  const side = sides.sort((a, b) => a[1] - b[1])[0][0];
  const extent = side === 'east' || side === 'west' ? room.d : room.w;
  const raw = side === 'east' || side === 'west' ? local.z : local.x;
  const offset = Math.max(-extent / 2 + .75, Math.min(extent / 2 - .75, Math.round(raw * 4) / 4));
  return { room, side, offset };
}
export function wallPoint(room: Room, door: Pick<Door, 'side' | 'x' | 'z'>): Point {
  return { x: room.origin.x + (door.side === 'east' ? room.w / 2 : door.side === 'west' ? -room.w / 2 : door.x), z: room.origin.z + (door.side === 'south' ? room.d / 2 : door.side === 'north' ? -room.d / 2 : door.z) };
}
export function connectRooms(a: Endpoint, b: Endpoint): [Door, Door] {
  if (!Number.isFinite(a.offset) || !Number.isFinite(b.offset)) throw new Error('Door offsets must be finite numbers.');
  if (a.room === b.room || a.room.id === b.room.id) throw new Error('Connect two different rooms.');
  if (a.room.doors.length >= 16 || b.room.doors.length >= 16) throw new Error('Up to 16 doors per room.');
  const token = `${Date.now().toString(36)}-${++serial}`;
  const first: Door = { id: `door-${token}-a`, side: a.side, ...doorPosition(a.room, a.side, a.offset), to: b.room.id, targetDoor: `door-${token}-b` };
  const second: Door = { id: first.targetDoor, side: b.side, ...doorPosition(b.room, b.side, b.offset), to: a.room.id, targetDoor: first.id };
  const issue = doorIssue(a.room, first) ?? doorIssue(b.room, second);
  if (issue) throw new Error(issue);
  a.room.doors.push(first); b.room.doors.push(second);
  return [first, second];
}
export function repositionDoor(room: Room, door: Door, endpoint: Endpoint): void {
  if (endpoint.room !== room) throw new Error('Keep the door endpoint on its own room.');
  const proposed = { ...door, side: endpoint.side, ...doorPosition(room, endpoint.side, endpoint.offset) };
  const issue = doorIssue(room, proposed, door.id); if (issue) throw new Error(issue);
  Object.assign(door, proposed);
}
export function removeConnection(stage: Stage, room: Room, door: Door): void {
  room.doors = room.doors.filter(d => d.id !== door.id);
  for(const c of room.cameras)if(c.door===door.id)delete c.door;
  const target = stage.rooms.find(r => r.id === door.to);
  if (target) {target.doors = target.doors.filter(d => d.id !== door.targetDoor);for(const c of target.cameras)if(c.door===door.targetDoor)delete c.door;}
}
export function connections(stage: Stage): { room: Room; door: Door; target: Room; other: Door }[] {
  const seen = new Set<string>(); const result = [];
  for (const room of stage.rooms) for (const door of room.doors) {
    const target = stage.rooms.find(r => r.id === door.to), other = target?.doors.find(d => d.id === door.targetDoor);
    const key = [room.id + '/' + door.id, door.to + '/' + door.targetDoor].sort().join('|');
    if (target && other && !seen.has(key)) { result.push({ room, door, target, other }); seen.add(key); }
  }
  return result;
}
export { doorOffset };