Skip to main content

Private runs

One party enters a run, plays down floors the hub generates from the run's seed, and the run ends cleared, wiped, abandoned or expired: the dungeon and roguelike shape. A seed replays its layout; the hub resolves each room itself (a fight from the party's strength and a seeded roll, a treasure, a rest; replace Run::resolve with your combat, or play the room out in an arena or a session); a party with nobody standing is wiped (permadeath); a run nobody touches for idle_ms expires. With a board in the config, a cleared or wiped run posts each member's depth to that leaderboard (the manifest lets instance call leaderboard). With a volume, opening a run reserves a grid of the world for it and grants the party's members the config's permission keys on it, which needs the grids.write and permissions.write scopes (add them in the plan's overrides).

Starter: crate instance, node type instance, keyed by the run id (one the game makes up per run). Module: kit.instances.run(key) in CrowdyJS, instances().run(key) in CrowdyCPP, kit.instances.run(key) in CrowdyPy.

Endpoints​

EndpointWhoWhat
open { seed }a player, whose run it is (a developer, for { party })Answers the seed: started, bad_request (party size), conflict (its grid could not be reserved)
invite { player }the ownerforbidden, full
joinan invited playerforbidden
entera member standingResolves the room the party is at; answers its log line: not_member, forbidden (down), conflict (descend first), finished
descendthe ownerAnswers the new floor: not_ready (rooms left), forbidden
abandonthe owner, or a developerEnds the run
stateanyoneThe run

Every change takes an action_id, answered once; refusals carry the codes above (see the protocol).

Topics​

run: snapshots of the run (phase, floor, rooms, members, gold, relics, log, ...).

Config​

A type's seed (seed_b64 in the manifest, the same for every key) is the starter's JSON config; what it leaves out keeps its default. floors (3), rooms_per_floor (4), party_max (4), idle_ms (30 min), start_hp, start_power, board, volume, member_permissions.

CrowdyJS​

const run = kit.instances.run(runId);
await run.watch();
const seed = await run.open({ seed: 7 });
await run.invite(friendId);
while (run.view.get()?.phase === 'running') {
log(await run.enter());
if (run.view.get()?.rooms.every((r) => r.done)) await run.descend();
}

CrowdyCPP​

auto& run = kit->instances().run(runId);
run.open({.seed = 7}, [&](crowdy::kit::KitReply r) {
if (r.ok()) run.invite(friendId, [](crowdy::kit::KitReply) {});
});
run.enter([](crowdy::kit::KitReply r) { log(r.value().asString()); });
game.poll(); // replies, topic messages and mirror changes arrive here

CrowdyPy​

run = kit.instances.run(run_id)
await run.watch()
await run.open(seed=7)
await run.invite(friend_id)
line = await run.enter()