/** * Coming into sight and going out of it, as arithmetic. The case that * matters is a bot crossing a doorway: it should arrive over a tenth of a * second from the side it came from, and leave a mark where it went. */ import { expect, test } from "bun:test"; import { ENTRY, FADE, forget, GHOST, ghosts, makePresence, REACH, shown, sighted, slide, SMALL, swell, } from "../static/presence.js"; const body = (over: Record = {}) => ({ id: "them", x: 400, y: 200, angle: 0, alive: true, mate: false, agent: "sable", ...over, }); const here = (x: number, y: number) => ({ x, y, angle: 0 }); test("a body just in sight is drawn in over a tenth of a second", () => { const p = makePresence(); sighted(p, [body()], 10); expect(shown(p, "them", 10)).toBe(0); expect(shown(p, "them", 10 + FADE / 2)).toBeCloseTo(0.5, 5); expect(shown(p, "them", 10 + FADE * 1.01)).toBe(1); expect(shown(p, "them", 30)).toBe(1); // It starts small and reaches its own size, never past it. expect(swell(0)).toBe(SMALL); expect(swell(1)).toBe(1); expect(swell(4)).toBe(1); }); test("a body already in sight is not faded in again by the next snapshot", () => { const p = makePresence(); sighted(p, [body()], 10); sighted(p, [body({ x: 420 })], 10.03); sighted(p, [body({ x: 440 })], 10.06); expect(shown(p, "them", 10.06)).toBeCloseTo(0.5, 5); expect(shown(p, "them", 10.2)).toBe(1); }); test("somebody nobody has ever seen is solid, not invisible", () => { const p = makePresence(); expect(shown(p, "nobody", 10)).toBe(1); expect(slide(p, "nobody", here(1, 2), 10, here(0, 0))).toEqual(here(1, 2)); }); test("a body that steps back into view slides in from the way it came", () => { const p = makePresence(); sighted(p, [body({ x: 400, y: 200 })], 10); // seen at the door sighted(p, [], 10.1); // and gone behind the wall sighted(p, [body({ x: 500, y: 200 })], 11); // back, further along const out = here(0, 0); // It begins a stride back down the line it walked, not at its destination. const start = slide(p, "them", here(500, 200), 11, out); expect(start.x).toBeCloseTo(500 - ENTRY, 5); expect(start.y).toBeCloseTo(200, 5); // Half way through the fade it is half way in. const half = slide(p, "them", here(500, 200), 11 + FADE / 2, out); expect(half.x).toBeCloseTo(500 - ENTRY / 2, 5); // And once it is in, the drawing is the interpolation's, untouched. const done = slide(p, "them", here(500, 200), 11 + FADE * 1.01, out); expect(done).toEqual(here(500, 200)); }); test("a body that turns up across the map does not slide across it", () => { const p = makePresence(); sighted(p, [body({ x: 100, y: 100 })], 10); sighted(p, [], 10.1); sighted(p, [body({ x: 100 + REACH + 10, y: 100 })], 12); const out = here(0, 0); const at = here(100 + REACH + 10, 100); expect(slide(p, "them", at, 12, out)).toEqual(at); // It still fades in: an arrival, just not a walk from anywhere. expect(shown(p, "them", 12)).toBe(0); }); test("a body that has not moved since has no direction to come from", () => { const p = makePresence(); sighted(p, [body({ x: 300, y: 300 })], 10); sighted(p, [], 10.1); sighted(p, [body({ x: 300.5, y: 300 })], 11); const at = here(300.5, 300); expect(slide(p, "them", at, 11, here(0, 0))).toEqual(at); }); test("where somebody was last seen is marked, and the mark dims away", () => { const p = makePresence(); sighted(p, [body({ x: 640, y: 480, agent: "veil" })], 10); expect(ghosts(p, 10)).toEqual([]); sighted(p, [], 10.5); const fresh = ghosts(p, 10.5); expect(fresh.length).toBe(1); expect(fresh[0]).toMatchObject({ id: "them", x: 640, y: 480, agent: "veil", fade: 1 }); expect(ghosts(p, 10.5 + GHOST / 2)[0]!.fade).toBeCloseTo(0.5, 5); expect(ghosts(p, 10.5 + GHOST)).toEqual([]); // And it is forgotten, not kept for ever. expect(p.last.size).toBe(0); }); test("a body last seen dead leaves no mark: a corpse is not a question", () => { const p = makePresence(); sighted(p, [body({ alive: false })], 10); sighted(p, [], 10.2); expect(ghosts(p, 10.2)).toEqual([]); }); test("coming back into sight takes the mark away at once", () => { const p = makePresence(); sighted(p, [body({ x: 400 })], 10); sighted(p, [], 10.2); expect(ghosts(p, 10.3).length).toBe(1); sighted(p, [body({ x: 430 })], 10.4); expect(ghosts(p, 10.4)).toEqual([]); }); test("a body in sight the whole time is never marked and never re-faded", () => { const p = makePresence(); for (let i = 0; i < 20; i++) sighted(p, [body({ x: 400 + i * 5 })], 10 + i * 0.033); expect(ghosts(p, 10.7)).toEqual([]); expect(shown(p, "them", 10.7)).toBe(1); }); test("a new round forgets everybody", () => { const p = makePresence(); sighted(p, [body()], 10); sighted(p, [], 10.2); forget(p); expect(ghosts(p, 10.3)).toEqual([]); expect(p.live.size).toBe(0); // And the next sighting is a first sighting, with nothing to slide from. sighted(p, [body({ x: 900 })], 11); expect(shown(p, "them", 11)).toBe(0); const at = here(900, 200); expect(slide(p, "them", at, 11, here(0, 0))).toEqual(at); }); test("two bodies are tracked apart", () => { const p = makePresence(); sighted(p, [body({ id: "a" }), body({ id: "b", x: 800 })], 10); sighted(p, [body({ id: "a" })], 10.2); expect(shown(p, "a", 10.2)).toBe(1); const marks = ghosts(p, 10.2); expect(marks.map((m) => m.id)).toEqual(["b"]); });