npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pleasantmachines/kento-arena

v0.1.0

Published

A strategy map's ground as data: a square grid of levels joined by ramps, authored once for seat 0 and copied for the others by the map's symmetry, rasterised to the masks a game walks and builds by, and checked for the things that make a map unplayable o

Readme

@kento/arena

A strategy map's ground as data: a square grid of levels joined by ramps, authored once for seat 0 and copied for the others by the map's symmetry, rasterised to the masks a game walks and builds by, and checked for the things that make a map unplayable or unfair. Pure and deterministic (integers and arithmetic; its only import is @kento/navigation's flow fields). src/index.ts is the only public entry; symmetryCopies and the transform* copies live in src/symmetry.ts, apart from the raster, so a first script that only asks how many seats a symmetry holds does not carry the rasteriser. No game nouns: what a start, a resource field or an objective is stays the game's.

Contract

  • Laying out (ArenaLayout, src/layout.ts): a generator writes places in authored coordinates, new ArenaLayout(size, symmetry, swap, edge) holds each clear in every copy (free(rect, margin), take(rect), copies, flip, toMap, cellToMap), with arenaRect, growRect and rectsOverlap for the rectangles.
  • Source (ArenaSource): size (cells a side), levels, symmetry, areas (levels painted in order over level 0), ramps (a rectangle, its foot level and the way it climbs), blocks (nothing walks, builds or sees through), covers (walked on, never built on) and breakables (blockers a match may take away). Rectangles are cells, inclusive.
  • Symmetry (ArenaSymmetry): none, rotate2 (half turn, 1v1), rotate4 (quarter turns, four seats), mirror-x, mirror-z. transformPoint, transformRect, transformCell (a footprint's first cell; quarter turns swap its sides), transformClimb and expandBySymmetry give copy k; copy 0 is the identity.
  • Raster (rasterArena): level, ramp (index into ramps or -1), cliff (a cell over a lower one that is no ramp, the border ring, a ramp's flanks across its climb: a ramp is entered only at its ends), block (blockers and standing breakables), cover, breakable (the standing breakable a cell belongs to, or -1) and breakables (every breakable's cells, source and copy). It throws on a bad source; sourceProblems names the problems first.
  • Masks (arenaMasks(raster, footprints)): walk (no cliff, blocker or solid footprint) and build (walkable, no cover, no ramp). footprintFits adds the one-level rule. arenaHeightAt(raster, frame, x, z) is the ground's height in the game's units, blended along a ramp.
  • Connectors (connect.ts: a siege tower's ramp, a lift, rubble over a wall: ramps made at runtime across a cliff edge, standing for a while): connectArena(base, connectors) is the authored raster with each connector (an ArenaRamp) rasterised as a ramp over the cliff cells it covers (source -1, copy its index) and the cliffs worked out again, so arenaMasks walks it, arenaHeightAt climbs it and a vision map reading level sees from it as from its foot, exactly as an authored ramp; connectArena(base, []) is base cell for cell, so taking a connector away is connecting the rest again. connectorProblems(raster, c, footprints?) is the runtime check (off-arena, not-an-edge: every cell a cliff one level over its foot, no blocker, ramp or other connector; foot and head: open ground past either end at its level once it stands; shut), and findConnectors(raster, at, reach, footprints?) lists the one-cell connectors whose foot is within reach of a cell, nearest first. The authored map's checks (checkArena: fairness, symmetry, ramp widths) are for the raster as authored: a connected raster is deliberately not its own copy. Pathing over a changed ground is the game's rebuild (its masks into new @kento/navigation fields or guides, or a region grid's set).
  • Breakables (breakables.ts: a rubble plug sealing a tunnel, a barricade, a fallen wall): authored like blockers and copied by the symmetry, each copy its own breakable, and rasterised into block, so while they stand every mask, path and check takes them as rock. breakArena(base, broken) is base with those breakables' cells open again (out of block and breakable; levels, ramps, cliffs and covers base's own arrays), so arenaMasks walks and builds them, flow fields over its walk mask go through, and a vision map whose blockers are its block sees through at its next update (@kento/vision reads its blockers each update); breakArena(base, []) is base cell for cell, and it composes with connectArena. A breakable overlaps no blocker and no other breakable or copy (sourceProblems), so breaking one opens all its cells. standingBreakables(raster) lists the ones standing; breakableCopy(raster, k, t) is breakable k's counterpart under copy t (a seat's state with k broken is fair against its counterpart seat's with that one broken), and breakableStates(raster, most?) every set that may stand broken (2^n, the empty set first), what a map's checks walk to cover every state. checkArena checks the raster it is given: a state with every copy of a breakable broken is its own copy, one with some broken is not. What breaks one (its health, who may strike it) is the game's.
  • Painting (an editor's brush): levelGrid is the areas' levels per cell, paintLevels(grid, src, cells, brush) raises, lowers, sets or flattens cells with all their copies (cellCopies), and areasOf(grid, size, symmetry) folds a painted grid back into the fewest rectangles over the authored part (each orbit's least-index cell), so the edited source rasterises to exactly the painted levels.
  • Objects (AuthoredObject): the game's kinds as footprints: owned (copy k goes to team k) or neutral (copies landing on the same cells are one), solid, start, slot, ground (build, walk or any) and a group naming counterparts. expandObjects makes the copies. Teams and seats: each copy of the authored part is a team; every start authored in it is one of the team's seats, its slot (default 0) saying which, and copy k's start of slot s is seat k + copies * s (ArenaObject.seat; team is the copy). One start a copy is a free-for-all of copies seats (seats are teams); two starts under a half turn is a 2v2 (seats 0 and 2 against 1 and 3). No symmetry, seats by hand (none: an asymmetric map, a campaign's): nothing is copied, and an owned object's seat (default its slot, else 0) and team (default its seat) are the author's, so a seat holds any number of objects, a start or none, and two seats may share a team. A symmetry ignores seat and team.
  • Validator (checkArena(raster, objects, { seats, tolerance, fair })): the report (each seat's octile path cost from its start to every object, a fairness row per group and relation (its team's own, neutral, another team's) with its spread over the seats of one slot (a team's two seats stand differently and are not each other's counterparts; each team's first seats are), each ramp's width) and problems coded unreachable, unfair, ramp-blocked (shut cells, or no open ground at its foot or head), overlap, placement, asymmetric (the raster is not its own copy) and seats. Path costs are integers (10 a straight cell, 14 a diagonal), so a symmetric map is exactly fair and tolerance defaults to 0. fair (default: unless the symmetry is none) says whether a spread is a problem; an asymmetric map's rows are reported only.
  • Regions (ArenaRegion, createRegions(regions, frame), regionProblems(regions, size)): named places a script asks about, a rectangle of cells or a circle round a cell's centre r cells wide, authored in cells and read in the game's units through the frame: contains(name, x, z) (a rectangle is its cells' area, its far edge the next cell's; a circle its disc), at(x, z) (the names holding a point), inside(name, things) (the {x, z} things in it, in order), distance(name, x, z) (0 inside, else to its nearest edge), centre(name) and cells(name, size). A name the index lacks throws (the author's bug); regionProblems names an empty or repeated name and a shape off the arena. Regions are places, not ground: no mask, path or copy reads them, and a symmetric map's are authored whole.
  • Paths (arenaDistances(size, walk, goals), arenaCorridor(size, walk, from, to, slack)): path costs to goal cells, and the cells on a shortest path between two places (or one at most slack longer): a map's main paths, for a game's check of what stands on them.

Relief

createArenaRelief(raster, frame, options) gives the flat tiers a shape for the eye: rolling land and shallow gullies, falling to zero at cliffs, ramps, any change of level, the border and cells the game marks flat. A pure function of the raster and the seed. It changes no level, cell or path: a game adds at(x, z) to arenaHeightAt where it draws.

Ground mesh

@kento/arena/ground (src/ground.ts, its own subpath so a page that only rasterises a map does not carry it, and free of any renderer) draws the raster as one static mesh in typed arrays. arenaGroundMesh(raster, frame, { relief, lip, strip, curb, skirt, shade }) returns unindexed triangles: position, color, uv, normal, each shade.channels value, facing (-1 turned from shade.light to 1 toward it, on land that rolls) and topVerts, the split between the tops, ramp curbs and skirt and what follows them, the faces and lit lip strips, for two draws. What it draws:

  • Tops: a flat cell is one quad; a lip, a ramp and a cell sharing an edge with a ramp stay 2×2 so the midpoint is shared. Colour comes from shade.colourAt(cx, cz, x, z), per vertex, read at once so it may return one scratch object.
  • Lips: arenaLipShift and arenaLipPoint set each lip point back onto its high cell (ArenaLip: a set-back, a seeded wander and bay, outer corners rounded in, capped past the first cell standable says a body can stand on). A point a half cell in carries half the mean shift, so a deep lip never folds the mesh. Nothing touching a ramp moves, and the low side of an edge stays put, so a face's foot is on the raster's edge. Props on a lip read the same functions.
  • Faces: split at the edge's midpoint through a mid row, a ledge here and there, with strata between shade.rock and shade.rock2. A lit strip strip metres deep behind each lip reads a step by value, never a black line.
  • Ramps: a lit curb curb metres wide on the ramp's surface along each side the ramp climbs past.
  • Skirt (optional, ArenaSkirt): land past the arena's edge, level at the edge's height, then falling and darkening.
  • Relief: every vertex rides relief (a flat ground is createArenaRelief at height 0), smooth-lit where it rolls.

shade.channels names per-vertex values the caller reads from a mesh point; the faces hold 0 in each. A same-input call gives the same bytes. Also here: arenaEdgeDistance (cells to a plateau's edge), arenaCliffDistance (1 or 2 cells to another level), arenaReached (a four-sided flood), arenaCliffSlabs (where rock slabs stand along cliffs and ramp sides, from a caller's stream; the caller's meshes draw them) and arenaHash and arenaNoise, the seeded presentation noise the lips use, for a game dressing the same ground. Colours, materials, what paves or wears the ground stay the game's.

Users

A game's map kind can be an arena source plus its own features (halls, iron, vents, a gate, watch points, props); its content check runs checkArena on every map, and its grid rasterises the played map with it.

Known gaps

  • Connectors are rectangles climbing one level along an axis, like authored ramps; one crossing a corner or two levels is refused.

  • Levels are flat tiers; a smooth heightfield is a different ground. Ramps are axis-aligned rectangles climbing one level.

  • Sight blockers are the game's; the arena has no line-of-sight model of its own.

  • rotate4 with a non-square authored part relies on the author keeping to one quarter; overlaps between copies are reported, not prevented.