# Code Style

This document describes the conventions used in `@ironarachne/rng`. Most
formatting is enforced automatically by [Biome](https://biomejs.dev/); the rest
is convention that reviewers will look for.

Run the automated checks before opening a pull request:

```bash
npm run check   # lint + build + test
npm run lint:fix   # apply formatting and safe lint fixes
```

## Tooling

Biome is the single source of truth for formatting and linting. There is no
second formatter — do not add Prettier, dprint, or EditorConfig rules that
compete with `biome.json`. If a rule needs to change, change it in `biome.json`
so every contributor picks it up.

TypeScript is compiled with `strict: true` and `module: "NodeNext"`. Both
settings are deliberate; code that needs them relaxed should be reworked
instead.

## Formatting

- Two-space indentation, spaces only.
- Double quotes for strings.
- Semicolons are required.
- Trailing commas in multi-line literals, parameter lists, and argument lists.
- Imports are organized by Biome's `organizeImports` assist. Do not hand-sort
  them.

## Modules and imports

- Every source file is an ES module. There is no CommonJS in this package.
- Relative imports **must** carry a `.js` extension, even though the source is
  TypeScript. `module: "NodeNext"` requires the emitted specifier:

  ```typescript
  import { RNG } from "./local.js";
  ```

- Import types with `import type`, and re-export them with `export type`, so
  type-only imports are erased from the emitted JavaScript:

  ```typescript
  import type { WeightedEntry } from "./types.js";
  ```

- `src/index.ts` is the only public entry point. It re-exports and contains no
  logic of its own. Anything not re-exported there is internal and may change
  without a major version bump.

## File layout

| File            | Responsibility                                              |
| --------------- | ----------------------------------------------------------- |
| `src/index.ts`  | Public surface. Re-exports only.                             |
| `src/local.ts`  | The `RNG` class — all generation logic lives here.           |
| `src/global.ts` | Thin wrappers over a module-level `RNG` instance.            |
| `src/types.ts`  | Shared exported types and interfaces.                        |

New generation logic belongs on `RNG`. If it should also be available without
managing a seed, add a matching one-line wrapper in `src/global.ts` that
delegates to the shared instance — wrappers must never contain logic of their
own.

## Naming

- `camelCase` for functions, methods, variables, and parameters.
- `PascalCase` for classes, interfaces, and type aliases.
- `SCREAMING_SNAKE_CASE` for module-level constants (`ALPHANUMERIC`).
- Names describe the result, not the implementation: `bellFloat`, not
  `sumOfThreeFloats`.

## Types

- Annotate the return type of every exported function and public method, even
  when inference would get it right. It is part of the published contract.
- Prefer generics over `unknown` or `any` when a value passes through
  unchanged: `item<T>(items: T[]): T`.
- Interface members that are not meant to be mutated are marked `readonly`.
- `any` is not used in this codebase. Reach for a generic or a union first.

## Class conventions

- Members carry an explicit `public` or `private` modifier.
- Internal state (`seed`) and helpers (`stringToSeed`) are `private`. Only add
  to the public surface when callers genuinely need it.
- Fields are declared at the top of the class, before the constructor.

## Documentation comments

Every exported symbol — class, method, function, interface, and interface
member — carries a TSDoc block. These are the source for the published API docs
generated by TypeDoc, so they are not optional.

```typescript
/**
 * Returns a random integer between min and max (inclusive).
 * @param min The minimum value.
 * @param max The maximum value.
 * @returns A random integer between min and max.
 */
public int(min: number, max: number): number {
```

- Document every parameter with `@param` and every non-void return with
  `@returns`.
- Document thrown errors with `@throws`, describing each condition that throws.
- Write in the third person, present tense: "Returns a random integer", not
  "Return" or "This will return".
- Inline `//` comments explain *why*, not *what*. The existing
  `// Convert to 32bit integer` earns its place because the bitwise `|= 0` is
  not self-evident.

## Errors

Validate arguments up front, before doing any work, and throw a plain `Error`.
Messages are prefixed with the function name and a colon, and state the actual
offending value where it helps:

```typescript
throw new Error(
  `randomSet(): itemCount (${itemCount}) exceeds array length (${items.length})`,
);
```

Do not silently clamp, coerce, or return a fallback value for invalid input.

## Determinism

This is the constraint that matters most in this package.

- All randomness flows through `RNG.next()`. Never call `Math.random()` in
  `src/`.
- A given seed must always produce the same sequence. Changing the algorithm in
  `next()`, or changing how many times an existing method calls it, changes
  every downstream result and is a **breaking change** requiring a major
  version bump.
- Methods must not mutate their arguments unless that is the documented
  contract. `shuffle()` shuffles in place by design; `randomSet()` copies with
  `[...items]` before shuffling precisely because it must not.

This library is explicitly not cryptographically secure. Do not add APIs that
imply otherwise.

## Tests

Tests live in `tests/` and run under [Vitest](https://vitest.dev/).

- Seed the RNG explicitly so assertions are deterministic. Do not assert on the
  output of the unseeded global instance.
- Cover the boundaries: empty arrays, zero and fractional weights, counts equal
  to the array length, and every documented `@throws` condition.
- Assert that non-mutating methods leave their input untouched.

## Dependencies

This package ships with zero runtime dependencies, and that is a feature. New
runtime dependencies need a strong justification in the pull request. Dev
dependencies should earn their place too — a tool that overlaps with one
already here should replace it rather than sit alongside it.
