Finishes the last two of the eleven manifests in this repo. Exact-pins every devDependency and takes three majors -- @testing-library/jest-dom 6 -> 7, jsdom 29 -> 30, pnpm 10.13.1 -> 11.24.0 -- with lint and coverage green in both (ts_core 42/42, web_ui 102/102, 100% branches). **TypeScript stays at 6.0.3, and this is the campaign's first real unsatisfiable conflict.** TS 7.0.2 is latest, but typescript-eslint 8.68.0 -- itself the latest release -- declares `typescript: ">=4.8.4 <6.1.0"` and throws "does not support TS 7.0" as eslint loads its config. There is no 9.x. Taking TS 7 would mean dropping every type-aware lint rule from both packages, so the compiler waits for the linter. Recorded as a `transitive:` allowlist entry, which is a predicate rather than a date: the gate re-checks the blocker each run and the entry clears itself when upstream ships support. First use of the allowlist, and the loud banner now prints on every run here. **pnpm 11 also made the gate wrong, and the gate was fixed rather than pnpm.** pnpm 11 quarantines packages published inside a 24h window -- a supply-chain measure against the period when a compromised publish is live -- and refused @testing-library/react 16.3.3, published 18h earlier. Reporting a repo as behind a version its package manager will not install is a finding nobody can act on: the hook fails, and so does the fix it asks for. `quarantine.py` now narrows npm's "latest" to the newest release old enough to install, bounded above by `dist-tags.latest` so a maintenance-line release never outranks the tag. It fetches the FULL packument (7MB for react, 16MB for typescript) and so runs only after a finding already exists -- a clean run pays nothing, which a test asserts by making the fetch raise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK |
||
|---|---|---|
| .. | ||
| dist | ||
| src | ||
| .gitignore | ||
| eslint.config.js | ||
| package.json | ||
| pnpm-lock.yaml | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.lint.json | ||
| vitest.config.ts | ||
@kuhyx/ts-core
Framework-free TypeScript utilities shared across kuhy's games and web apps.
Deliberately no React and no CSS — those live in
@kuhyx/web-ui. A game that needs a seeded RNG should not have
to install a UI package to get one; that coupling is what makes people vendor
their own copy instead, which is how these ended up duplicated four ways.
Install
pnpm only. npm cannot install a subdirectory of a git repo — it ignores
the &path: key, looks for package.json at the repo root and fails with
ENOENT. Every consumer of this package must be on pnpm.
// package.json
"dependencies": {
"@kuhyx/ts-core": "github:kuhyx/utils#ts_core-v0.1.0&path:/ts_core"
}
What it ships
| Export | Notes |
|---|---|
Clock, createRealClock, realClock, createManualClock, ManualClock |
Injectable time source, so real-time mechanics are driven by a scripted clock in tests instead of racing the wall clock. |
Rng, createRng, nextFloat, nextInt, nextChance, pick |
Seeded mulberry32, free-function style over mutable state. |
SeededRng, createSeededRng |
The same core, closure style. |
Why the RNG has two interfaces
The repos that had grown their own copy did not agree on shape: konbini-67
used mutable state plus free functions (nextFloat(rng)); sims3-clone and
europe-county-map returned an object of closures (rng.next() / rng.float()).
Both ship here rather than being reconciled into one, because the sequence is the contract. These repos have seeded tests and "same seed → identical world" guarantees, so changing how many times a helper advances the generator silently re-rolls every saved world. A shared package that did that would be worse than the duplication it replaced.
The generator core is identical in both, so a given seed produces the same float sequence whichever interface you use — asserted in the tests.
int is inclusive of both bounds, matching sims3-clone and europe-county-map.
konbini-67's local helper took a single exclusive max; it maps to
nextInt(rng, 0, max - 1).
The golden-sequence test
rng.test.ts pins the first five floats for seed 12345, captured from the
pre-extraction konbini-67 implementation. If that test fails, the shared
core has stopped reproducing the sequence every consumer's saved seeds depend
on. Regenerating those constants to make a failure go away defeats the entire
point of the test — fix the code instead.
hotline3d is deliberately not a consumer
hotline3d/hotline-kernel/src/core/rng.ts looks like the same generator but
uses % and Math.trunc where mulberry32 uses >>>/>>. Measured, it
produces a different sequence — so adopting this package would change its
generated output. Left alone on purpose.
Commands
pnpm install
pnpm test # vitest
pnpm coverage # 100% branches/functions/lines/statements, enforced
pnpm lint # tsc --noEmit + eslint (type-aware, strict)
pnpm build # tsc -> dist/ (ESM + .d.ts), committed to the tag
dist/ is committed, like web_ui's: Vite does not transpile dependencies,
so a consumer would otherwise need per-repo bundler config. CI fails if the
committed dist/ does not match a fresh build.