Public: anyone can read this; the crew can change it.

forked from trevato/cs · proposes

The engine has one door: src/engine/index.ts is the surface, and nothing outside reaches past it #28

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

torque/cs-engine:torque/25-the-engine-has-one-door-src-engine-index → main 1 commit fork

  • Merged. torque/25-the-engine-has-one-door-src-engine-index was merged into main as 25bbe1c.

Review

Sign in to review this change.
26 files changed +288 −88
README.md +17
@@ -66,6 +66,23 @@ the rest.
6666 them points at `src/client/`, at `src/rl/` or at a package. The last two take
6767 their clock, their canvas and their window as parameters, so the tests drive
6868 them with a stub instead of a browser.
69 - `src/engine/index.ts` — the door, and the rule is that a second game imports
70 `src/engine/index.ts` and nothing deeper; inside the engine, modules import
71 each other directly. The surface is re-exports and a docstring per module,
72 nothing else: from `math` `v3`, `add`, `sub`, `scale`, `mul`, `dot`, `cross`,
73 `length`, `length2d`, `dist`, `dist2d`, `normalize`, `lerp`, `clamp`, `rad`,
74 `deg`, `angleDelta`, `angleVectors`, `mat4`, `multiply`, `perspective`,
75 `viewMatrix`, `modelMatrix`, `rng`, `Vec3` and `Mat4`; from `trace` `rayBox`,
76 `traceWorld`, `visible`, `segmentLength`, `Box` and `Hit`; from `grid`
77 `inside`, `cellAt`, `worldOf`, `colOf`, `rowOf`, `mergeSolid`, `findPath` and
78 `Grid`; from `mesh` `builder`, `addBox`, `finish`, `quadMesh`, `Builder` and
79 `Mesh`; from `texture` `hash`, `noise`, `fbm`, `paintAtlas` and `Shader`; from
80 `sound` `attenuate`, `createSoundDevice`, `Burst`, `Tone`, `SoundDevice` and
81 `Ctor`; from `loop` `createLoop`, `Frames`, `Loop` and `LoopOptions`; and from
82 `controls` `createControls`, `attachButtons` and the event and option types
83 they are driven with. `boundary.test.ts` fails a deep import from outside, a
84 module that is not behind the door, and any change to that list of names that
85 is not also a change to the list written out in the test.
6986 - `src/client/` — the game, in modules named for one thing each: `map` (de_forge
7087 itself: its areas, its crates, its cell alphabet and its spawns and sites, laid
7188 over the engine's grid), `physics`, `weapons`, `combat`, `match` (the rules),
scripts/train.ts +1 −1
@@ -15,7 +15,7 @@ import { writeFileSync } from "node:fs";
1515 import { join } from "node:path";
1616 import { HEADS, decode } from "../src/client/action.ts";
1717 import { botThink, makeBrain } from "../src/client/bots.ts";
18 import { rng } from "../src/engine/math.ts";
18 import { rng } from "../src/engine/index.ts";
1919 import { OBS_SIZE } from "../src/client/observe.ts";
2020 import {
2121 copyPolicy, createPolicy, forward, greedy, newActivations, pack, sample, unpack,
src/client/audio.test.ts +1 −1
@@ -1,6 +1,6 @@
11 import { expect, test } from "bun:test";
22 import { createAudio, loudness, shotTone } from "./audio.ts";
3 import type { Burst, SoundDevice, Tone } from "../engine/sound.ts";
3 import type { Burst, SoundDevice, Tone } from "../engine/index.ts";
44
55 /** A device that plays nothing and writes down everything. */
66 function recorder(): SoundDevice & { bursts: Burst[]; tones: Tone[]; resumes: number } {
src/client/audio.ts +1 −2
@@ -5,8 +5,7 @@
55 * only opens a context once the person has clicked something.
66 */
77
8 import { dist, type Vec3 } from "../engine/math.ts";
9 import { attenuate, createSoundDevice, type SoundDevice } from "../engine/sound.ts";
8 import { attenuate, createSoundDevice, dist, type SoundDevice, type Vec3 } from "../engine/index.ts";
109 import { WEAPONS, type WeaponId } from "./weapons.ts";
1110
1211 export type Audio = {
src/client/binding.ts +1 −1
@@ -5,7 +5,7 @@
55 * hands the simulation, so a person and a bot are the same kind of player.
66 */
77
8 import { clamp } from "../engine/math.ts";
8 import { clamp } from "../engine/index.ts";
99 import { NO_INPUT, type Input } from "./world.ts";
1010
1111 /** `KeyboardEvent.code` to action. Today's bindings, moved rather than tuned. */
src/client/bots.ts +1 −1
@@ -12,7 +12,7 @@
1212 */
1313
1414 import { decode, HEADS, STILL, type Intent } from "./action.ts";
15 import { angleDelta, clamp, dist, rng, type Vec3 } from "../engine/math.ts";
15 import { angleDelta, clamp, dist, rng, type Vec3 } from "../engine/index.ts";
1616 import * as map from "./map.ts";
1717 import { observe, OBS_SIZE, type Memory } from "./observe.ts";
1818 import {
src/client/combat.test.ts +1 −1
@@ -1,7 +1,7 @@
11 import { expect, test } from "bun:test";
22 import { applyHit, hitboxes, traceBullet, type Actor } from "./combat.ts";
33 import type { Box } from "./map.ts";
4 import type { Vec3 } from "../engine/math.ts";
4 import type { Vec3 } from "../engine/index.ts";
55 import { WEAPONS } from "./weapons.ts";
66
77 const actor = (over: Partial<Actor> = {}): Actor => ({
src/client/combat.ts +1 −2
@@ -5,9 +5,8 @@
55 */
66
77 import type { Box } from "./map.ts";
8 import { dist, type Vec3 } from "../engine/math.ts";
8 import { dist, rayBox, traceWorld, type Vec3 } from "../engine/index.ts";
99 import { HULL } from "./physics.ts";
10 import { rayBox, traceWorld } from "../engine/trace.ts";
1110 import { computeDamage, type Hitgroup, type Weapon } from "./weapons.ts";
1211
1312 export type Actor = {
src/client/hud.ts +1 −1
@@ -4,7 +4,7 @@
44 * it never changes anything.
55 */
66
7 import { clamp } from "../engine/math.ts";
7 import { clamp } from "../engine/index.ts";
88 import { MOVE } from "./physics.ts";
99 import { inaccuracy, WEAPONS } from "./weapons.ts";
1010 import { currentWeapon, useProgress, type World } from "./world.ts";
src/client/main.ts +1 −3
@@ -9,9 +9,7 @@
99 import { createAudio } from "./audio.ts";
1010 import { CS_KEYS, CS_MOUSE, CS_TAP, readIntent } from "./binding.ts";
1111 import { drawHud } from "./hud.ts";
12 import { attachButtons, createControls } from "../engine/controls.ts";
13 import { createLoop } from "../engine/loop.ts";
14 import { clamp } from "../engine/math.ts";
12 import { attachButtons, clamp, createControls, createLoop } from "../engine/index.ts";
1513 import { COMPETITIVE, SHORT, type Rules } from "./match.ts";
1614 import { createRenderer, type Camera } from "./renderer.ts";
1715 import { buyable } from "./weapons.ts";
src/client/map.ts +3 −4
@@ -12,9 +12,8 @@
1212 * about this alphabet: what counts as solid or walkable goes in as a predicate.
1313 */
1414
15 import * as g from "../engine/grid.ts";
16 import type { Vec3 } from "../engine/math.ts";
17 import type { Box } from "../engine/trace.ts";
15 import * as g from "../engine/index.ts";
16 import type { Box, Vec3 } from "../engine/index.ts";
1817
1918 export const CELL = 128;
2019 export const GRID_W = 44;
@@ -92,7 +91,7 @@ export const rowOf = (y: number): number => g.rowOf(grid, y);
9291 export const MAT = { FLOOR: 0, WALL: 1, CRATE: 2, SITE: 3, METAL: 4 } as const;
9392
9493 /** The tracer owns the box; the map hands it out, because that is where it is written. */
95 export type { Box } from "../engine/trace.ts";
94 export type { Box } from "../engine/index.ts";
9695
9796 const mergeSolid = (match: (c: Cell) => boolean, z0: number, z1: number, mat: number): Box[] =>
9897 g.mergeSolid(grid, match, z0, z1, mat);
src/client/materials.ts +1 −1
@@ -6,7 +6,7 @@
66 * painted into.
77 */
88
9 import { fbm, hash, paintAtlas, type Shader } from "../engine/texture.ts";
9 import { fbm, hash, paintAtlas, type Shader } from "../engine/index.ts";
1010
1111 export const TEX_SIZE = 128;
1212
src/client/models.ts +1 −1
@@ -4,7 +4,7 @@
44 * engine's box builder, which keeps the whole game one download.
55 */
66
7 import { addBox, builder, finish, type Builder, type Mesh } from "../engine/mesh.ts";
7 import { addBox, builder, finish, type Builder, type Mesh } from "../engine/index.ts";
88 import type { Box } from "./map.ts";
99
1010 /** The level, as one buffer. Fifty-odd boxes, three hundred quads, one draw. */
src/client/observe.ts +1 −1
@@ -13,7 +13,7 @@
1313 */
1414
1515 import * as map from "./map.ts";
16 import { angleDelta, clamp, dist, type Vec3 } from "../engine/math.ts";
16 import { angleDelta, clamp, dist, type Vec3 } from "../engine/index.ts";
1717 import { HULL } from "./physics.ts";
1818 import { inaccuracy, WEAPONS } from "./weapons.ts";
1919 import type { Player, World } from "./world.ts";
src/client/physics.test.ts +1 −1
@@ -1,6 +1,6 @@
11 import { expect, test } from "bun:test";
22 import type { Box } from "./map.ts";
3 import type { Vec3 } from "../engine/math.ts";
3 import type { Vec3 } from "../engine/index.ts";
44 import { accelerate, airAccelerate, fallDamage, HULL, MOVE, move, type Body, type Wish } from "./physics.ts";
55
66 const ground: Box = { min: [-1000, -1000, -32], max: [1000, 1000, 0], mat: 0 };
src/client/physics.ts +1 −1
@@ -9,7 +9,7 @@
99 */
1010
1111 import type { Box } from "./map.ts";
12 import { clamp, dot, normalize, type Vec3 } from "../engine/math.ts";
12 import { clamp, dot, normalize, type Vec3 } from "../engine/index.ts";
1313
1414 export const HULL = { radius: 16, stand: 72, duck: 54, eyeStand: 64, eyeDuck: 46 };
1515
src/client/policy.test.ts +1 −1
@@ -1,6 +1,6 @@
11 import { expect, test } from "bun:test";
22 import { AXIS_BINS, decode, HEADS, PITCH_BINS, toInput, YAW_BINS } from "./action.ts";
3 import { rng } from "../engine/math.ts";
3 import { rng } from "../engine/index.ts";
44 import {
55 createPolicy, forward, greedy, headOffset, logitCount, newActivations, pack, paramCount,
66 sample, softmax, unpack, type Shape,
src/client/renderer.ts +1 −2
@@ -5,8 +5,7 @@
55 * the frame loop except the numbers that change.
66 */
77
8 import { multiply, perspective, viewMatrix, modelMatrix, type Vec3 } from "../engine/math.ts";
9 import type { Mesh } from "../engine/mesh.ts";
8 import { multiply, perspective, viewMatrix, modelMatrix, type Mesh, type Vec3 } from "../engine/index.ts";
109 import { bombMesh, levelMesh, playerMesh, viewModelMesh } from "./models.ts";
1110 import { LAYERS, TEX_SIZE, materialTextures } from "./materials.ts";
1211 import type { World } from "./world.ts";
src/client/weapons.ts +1 −1
@@ -8,7 +8,7 @@
88 * bullets every round, which is the property that makes spray control a skill.
99 */
1010
11 import { rng } from "../engine/math.ts";
11 import { rng } from "../engine/index.ts";
1212
1313 export type WeaponId =
1414 | "knife" | "glock" | "usp" | "deagle" | "mp9" | "ak47" | "m4a4" | "awp";
src/client/world.ts +1 −2
@@ -9,10 +9,9 @@
99
1010 import { applyHit, traceBullet, type Actor } from "./combat.ts";
1111 import * as map from "./map.ts";
12 import { angleVectors, angleDelta, clamp, dist, normalize, rng, sub, type Vec3 } from "../engine/math.ts";
12 import { angleVectors, angleDelta, clamp, dist, normalize, rng, sub, visible, type Vec3 } from "../engine/index.ts";
1313 import * as rules from "./match.ts";
1414 import { eyeHeight, hullHeight, move, type Body, type Wish, MOVE } from "./physics.ts";
15 import { visible } from "../engine/trace.ts";
1615 import {
1716 ARMOR_PRICE, DEFUSE_KIT_PRICE, HELMET_PRICE, WEAPONS, inaccuracy, recoilOffset,
1817 type WeaponId, type Weapon,
src/engine/boundary.test.ts +140
@@ -8,6 +8,11 @@
88 *
99 * A test in here may also reach for the runner and for node's builtins, which
1010 * is how this file reads the sources; neither ships in the bundle.
11 *
12 * The same boundary read from the other side is `index.ts`, the engine's one
13 * door: nothing outside may name a deeper path, every module in here is behind
14 * the door, and the names the door offers are written out below so that adding
15 * or removing one is a line in a review rather than a silent widening.
1116 */
1217
1318 import { expect, test } from "bun:test";
@@ -15,6 +20,10 @@ import { readFileSync, readdirSync } from "node:fs";
1520 import { join, relative, resolve } from "node:path";
1621
1722 const ENGINE = import.meta.dir;
23 const ROOT = resolve(ENGINE, "..", "..");
24
25 /** The directories outside the engine whose imports this test is about. */
26 const OUTSIDE = ["src", "scripts"];
1827
1928 /** Every `.ts` under the engine, recursively, as paths relative to it. */
2029 export function sources(dir = ENGINE): string[] {
@@ -54,6 +63,49 @@ export function leavesEngine(file: string, specifier: string): string | null {
5463 return null;
5564 }
5665
66 /** Every `.ts` outside the engine, as paths relative to the repository root. */
67 export function outsideSources(dirs = OUTSIDE): string[] {
68 const out: string[] = [];
69 const walk = (dir: string): void => {
70 for (const entry of readdirSync(join(ROOT, dir), { withFileTypes: true })) {
71 const path = join(dir, entry.name);
72 if (resolve(ROOT, path) === ENGINE) continue;
73 if (entry.isDirectory()) walk(path);
74 else if (entry.name.endsWith(".ts")) out.push(path);
75 }
76 };
77 for (const dir of dirs) walk(dir);
78 return out.sort();
79 }
80
81 /**
82 * Why this file outside the engine may not import this specifier, or null if it
83 * may. Only one path into the engine is a path: `index.ts`, the door.
84 */
85 export function reachesPastTheDoor(file: string, specifier: string): string | null {
86 if (!specifier.startsWith(".")) return null;
87 const target = resolve(ROOT, join(file, ".."), specifier);
88 const inside = relative(ENGINE, target);
89 if (inside === "" || inside.startsWith("..")) return null;
90 if (inside === "index.ts") return null;
91 return `${file} imports "${specifier}"; the engine's door is src/engine/index.ts`;
92 }
93
94 /**
95 * The names a barrel re-exports: everything inside the braces of an
96 * `export { … } from` or `export type { … } from`, under the name it leaves by.
97 */
98 export function reExportedNames(source: string): string[] {
99 const out: string[] = [];
100 const pattern = /\bexport\s+(?:type\s+)?\{([^}]*)\}\s*from\s*["'][^"']+["']/g;
101 for (let m = pattern.exec(source); m !== null; m = pattern.exec(source))
102 for (const clause of (m[1] as string).split(",")) {
103 const name = clause.trim().replace(/^type\s+/, "").split(/\s+as\s+/).pop()?.trim();
104 if (name !== undefined && name !== "") out.push(name);
105 }
106 return out;
107 }
108
57109 test("the engine has the sources this test is about", () => {
58110 const files = sources();
59111 expect(files).toContain("math.ts");
@@ -100,3 +152,91 @@ test("the rule catches the imports it exists to catch", () => {
100152 expect(leavesEngine("boundary.test.ts", "node:path")).toBeNull();
101153 expect(leavesEngine("boundary.test.ts", "../client/map.ts")).toMatch(/outside/);
102154 });
155
156 test("nothing outside the engine imports a path past the door", () => {
157 const offences: string[] = [];
158 for (const file of outsideSources()) {
159 const source = readFileSync(join(ROOT, file), "utf8");
160 for (const specifier of specifiersOf(source)) {
161 const why = reachesPastTheDoor(file, specifier);
162 if (why !== null) offences.push(why);
163 }
164 }
165 expect(offences).toEqual([]);
166 });
167
168 test("the door rule is about the door and nothing else", () => {
169 expect(reachesPastTheDoor("src/client/world.ts", "../engine/math.ts")).toMatch(/door/);
170 expect(reachesPastTheDoor("src/rl/arena.ts", "../engine/sub/deep.ts")).toMatch(/door/);
171 expect(reachesPastTheDoor("src/client/world.ts", "../engine/index.ts")).toBeNull();
172 expect(reachesPastTheDoor("src/client/world.ts", "./physics.ts")).toBeNull();
173 expect(reachesPastTheDoor("src/client/world.ts", "bun:test")).toBeNull();
174 expect(reachesPastTheDoor("scripts/build.ts", "../src/engine/trace.ts")).toMatch(/door/);
175 });
176
177 test("every module in the engine is behind the door, and the door holds nothing else", () => {
178 const door = readFileSync(join(ENGINE, "index.ts"), "utf8");
179 const behind = new Set(specifiersOf(door).map((s) => s.replace(/^\.\//, "")));
180 const modules = sources().filter((f) => !f.endsWith(".test.ts") && f !== "index.ts");
181 expect(modules.length).toBeGreaterThan(0);
182 expect([...behind].sort()).toEqual(modules);
183 // Nothing of its own: the door is re-exports, a docstring and nothing else.
184 const body = door.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
185 expect(body.replace(/\bexport\s+(?:type\s+)?\{[^}]*\}\s*from\s*["'][^"']+["'];/g, "").trim()).toBe("");
186 });
187
188 test("no module in the engine goes through the engine's own door", () => {
189 const offences: string[] = [];
190 for (const file of sources()) {
191 // The door itself, and a test that reaches through it to check what it holds.
192 if (file === "index.ts" || file.endsWith(".test.ts")) continue;
193 const source = readFileSync(join(ENGINE, file), "utf8");
194 for (const specifier of specifiersOf(source))
195 if (relative(ENGINE, resolve(ENGINE, join(file, ".."), specifier)) === "index.ts")
196 offences.push(`${file} imports the door; a package does not go through its own door`);
197 }
198 expect(offences).toEqual([]);
199 });
200
201 /**
202 * The surface. This list is the engine's public API written down: a name that
203 * arrives, leaves or is renamed fails here and is read in the pull. Sorted the
204 * way `Array.prototype.sort` sorts, so capitals come first.
205 */
206 const SURFACE = [
207 "Box", "Builder", "Burst", "Button", "ButtonOptions", "Controls", "Ctor", "Frames", "Grid",
208 "Hit", "Host", "KeyEventish", "Listens", "LockOwner", "Loop", "LoopOptions", "Mat4", "Mesh",
209 "MouseEventish", "Options", "Shader", "SoundDevice", "Stick", "Surface", "Tone",
210 "TouchEventish", "Touchish", "Vec3", "add", "addBox", "angleDelta", "angleVectors",
211 "attachButtons", "attenuate", "builder", "cellAt", "clamp", "colOf", "createControls",
212 "createLoop", "createSoundDevice", "cross", "deg", "dist", "dist2d", "dot", "fbm",
213 "findPath", "finish", "hash", "inside", "length", "length2d", "lerp", "mat4", "mergeSolid",
214 "modelMatrix", "mul", "multiply", "noise", "normalize", "paintAtlas", "perspective",
215 "quadMesh", "rad", "rayBox", "rng", "rowOf", "scale", "segmentLength", "sub", "traceWorld",
216 "v3", "viewMatrix", "visible", "worldOf",
217 ];
218
219 test("the door offers exactly the names written down here", () => {
220 const offered = reExportedNames(readFileSync(join(ENGINE, "index.ts"), "utf8"));
221 expect(offered.length).toBe(new Set(offered).size);
222 expect(offered.sort()).toEqual(SURFACE);
223 });
224
225 test("the names come out of both halves of a re-export, alias and all", () => {
226 const F = "fr" + "om";
227 const source = [
228 `export { a, b as c } ${F} "./one.ts";`,
229 `export type { D, type E } ${F} "./two.ts";`,
230 `export {\n f,\n g,\n} ${F} "./three.ts";`,
231 ].join("\n");
232 expect(reExportedNames(source)).toEqual(["a", "c", "D", "E", "f", "g"]);
233 });
234
235 test("the values the door re-exports are the ones the modules hold", async () => {
236 const door = await import("./index.ts");
237 const math = await import("./math.ts");
238 expect(door.clamp).toBe(math.clamp);
239 expect(door.rng).toBe(math.rng);
240 const trace = await import("./trace.ts");
241 expect(door.rayBox).toBe(trace.rayBox);
242 });
src/engine/index.ts added +50
@@ -0,0 +1,50 @@
1 /**
2 * The engine's door. A package is what it exports, and this file is the whole
3 * of it: everything under `src/engine/` reaches the game through here, and
4 * nothing outside the engine names a deeper path. `boundary.test.ts` holds both
5 * halves of that — the rule against deep imports, and the pinned list of the
6 * names below, so a name arrives or leaves in a line a reviewer can see.
7 *
8 * There is no logic and no constant of its own in here, and no module it
9 * re-exports does work at import time: importing the door must stay as cheap as
10 * importing the one module you wanted. Inside the engine, modules import each
11 * other directly — a package does not go through its own door.
12 */
13
14 /** Vectors, matrices, angles and a seeded rng: the arithmetic under everything. */
15 export {
16 add, angleDelta, angleVectors, clamp, cross, deg, dist, dist2d, dot, length, length2d, lerp,
17 mat4, modelMatrix, mul, multiply, normalize, perspective, rad, rng, scale, sub, v3, viewMatrix,
18 } from "./math.ts";
19 export type { Mat4, Vec3 } from "./math.ts";
20
21 /** Rays against axis-aligned boxes, and the `Box` they are traced against. */
22 export { rayBox, segmentLength, traceWorld, visible } from "./trace.ts";
23 export type { Box, Hit } from "./trace.ts";
24
25 /** A grid of cells of any type: cell-to-world arithmetic, greedy meshing, A*. */
26 export { cellAt, colOf, findPath, inside, mergeSolid, rowOf, worldOf } from "./grid.ts";
27 export type { Grid } from "./grid.ts";
28
29 /** A box builder: positions, normals, uvs and a layer, finished into a `Mesh`. */
30 export { addBox, builder, finish, quadMesh } from "./mesh.ts";
31 export type { Builder, Mesh } from "./mesh.ts";
32
33 /** Hashed value noise, fbm, and an atlas painted from a list of shaders. */
34 export { fbm, hash, noise, paintAtlas } from "./texture.ts";
35 export type { Shader } from "./texture.ts";
36
37 /** A lazy `AudioContext` with noise bursts, tones and distance attenuation. */
38 export { attenuate, createSoundDevice } from "./sound.ts";
39 export type { Burst, Ctor, SoundDevice, Tone } from "./sound.ts";
40
41 /** A fixed simulation step with a clamp on a long frame, clock supplied. */
42 export { createLoop } from "./loop.ts";
43 export type { Frames, Loop, LoopOptions } from "./loop.ts";
44
45 /** A bindings table in, held actions out: keyboard, pointer lock and touch. */
46 export { attachButtons, createControls } from "./controls.ts";
47 export type {
48 Button, ButtonOptions, Controls, Host, KeyEventish, Listens, LockOwner, MouseEventish,
49 Options, Stick, Surface, TouchEventish, Touchish,
50 } from "./controls.ts";
src/rl/arena.ts +1 −1
@@ -15,7 +15,7 @@
1515 import { decode, toInput, type Intent } from "../client/action.ts";
1616 import { makeBrain, botThink } from "../client/bots.ts";
1717 import * as map from "../client/map.ts";
18 import { angleDelta, clamp, dist, rng, type Vec3 } from "../engine/math.ts";
18 import { angleDelta, clamp, dist, rng, type Vec3 } from "../engine/index.ts";
1919 import { observe, OBS_SIZE, type Memory } from "../client/observe.ts";
2020 import type { Input, Player, World } from "../client/world.ts";
2121 import { aimAt, applyInput, bodyCentre, canSee, createWorld, NO_INPUT } from "../client/world.ts";
src/rl/ppo.test.ts +1 −1
@@ -1,5 +1,5 @@
11 import { expect, test } from "bun:test";
2 import { rng } from "../engine/math.ts";
2 import { rng } from "../engine/index.ts";
33 import {
44 createPolicy, forward, newActivations, tensorsOf, type Policy, type Shape,
55 } from "../client/policy.ts";
static/app.3fa56501a8.js added +58

Diff not shown — a line 57,166 characters wide (limit 2,000). +58

View file
static/app.90bb5b2a7e.js deleted −58

Diff not shown — a line 57,162 characters wide (limit 2,000). −58

View file

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs