| Age | Commit message (Collapse) | Author | Files | Lines |
|
|
|
|
|
|
|
sample
|
|
|
|
database
|
|
|
|
and credits to their new homes
|
|
|
|
the mockup
|
|
|
|
return
|
|
|
|
for the leaf scene
|
|
prevailing wind
|
|
interactive
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
to routes
|
|
define; lib is folders-only
|
|
version to 1.0 from package.json
|
|
|
|
is the only allowed comment
|
|
doc references, JSDoc-only
|
|
|
|
|
|
|
|
|
|
Fonts (SIL Open Font License; licenses preserved alongside the files):
static/fonts/Literata-Regular.ttf body — 400 weight
static/fonts/Literata-Italic.ttf body — 400 italic
static/fonts/Literata-Medium.ttf body — 500 weight
static/fonts/Inter-Regular.ttf UI chrome — 400 weight
static/fonts/Inter-Medium.ttf UI chrome — 500 weight
static/fonts/Literata.OFL.txt
static/fonts/Inter.OFL.txt
Google Fonts' Inter distribution is optical-size-tiered — the 18pt
variants are tuned for the 13–18px range our UI uses, so they're
aliased as the base "Inter" family. Crimson Pro (memoir body) is not
yet sourced; its @font-face block and preload hint both drop out of
app.css / app.html for now and land when the memoir screen is built.
app.css swaps the woff2 references for ttf (format: 'truetype'), and
the @font-face comment now reflects the reality of what's on disk.
app.html preloads the ttf variants.
svelte.config.js drops the prerender handleHttpError shim that was
tolerating missing /fonts/ 404s — the files exist now, so the build
should fail loudly if any preloaded font goes missing.
Title-screen audio:
static/audio/title/piano-relaxing.mp3 — "Piano Relaxing"
by atlasaudio,
Pixabay Content
License
static/audio/title/piano-relaxing.license.txt — Pixabay license
certificate
Note on design: docs/17-first-hour.md said "no menu music" on the
title screen. Running with this addition per the director's current
direction (the design docs reflect an earlier stage of thinking).
|
|
Two related cleanups landed together because they touch the same pattern:
1. Intra-module imports now use relative paths rather than the module's
own path alias. rng/seeded.ts imports from './derive', not 'rng/derive';
time/gameTime.ts from './calendar', not 'time/calendar'; the utils
barrel from './result' etc. This matches rules/01-code-style.md:
"Relative imports only for same-directory siblings." The alias form
is reserved for imports *between* modules — how external callers refer
to a module's public surface.
(The TS language server in the IDE had trouble resolving the self-
aliased form even though svelte-check accepted it; this change
removes the ambiguity.)
2. utils/ is now folder-per-concept. Each utility owns a directory with
its files broken up into small pieces:
utils/result/ types, constructors, predicates, map, unwrap
utils/assert/ assert, assert-never, assert-defined
utils/equal/ deep
utils/types/ brand, deep-readonly, element-of, json, non-empty-array
utils/log/ log
utils/index.ts re-exports from each subfolder via the barrel. External
callers import from 'utils' unchanged; internal references are relative.
One reason to prefer folders over flat files here is headroom — when a
concept grows, new files sit alongside their siblings inside the
concept's folder rather than crowding the utils/ root.
No API changes. All 128 tests still green.
|
|
Foundational helpers referenced by most subsequent code. Small, pure,
no dependencies on anything in the project.
utils/result.ts Result<T, E> = Ok<T> | Err<E> for recoverable
failures at system boundaries (save/load, manifest
fetch, content validation). Internal pure logic
still uses throw for programmer errors.
ok / err / isOk / isErr / mapResult / mapErr /
unwrap / unwrapOr.
utils/assert.ts assert (with type-narrowing asserts), assertNever
for exhaustive-switch termination, assertDefined
for narrowing T | null | undefined → T.
utils/equal.ts Structural deepEqual for tests comparing simulation
snapshots. Handles plain objects, arrays, Map, Set,
Date, primitives. Does not handle cyclic graphs
(simulation state is a tree).
utils/types.ts Common type aliases: NonEmptyArray, JsonValue,
ElementOf, DeepReadonly, Brand<T, B> for nominal /
branded types (PersonId vs RelationshipId).
utils/log.ts Structured logging wrapping console. Developer-
facing only; the game ships with no runtime
telemetry.
utils/index.ts Public re-exports.
40 unit tests in tests/unit/utils/ cover Result constructors and
combinators, assert variants, and deepEqual's primitive / array /
object / Map / Set / Date paths plus a realistic simulation-snapshot
comparison.
|
|
The Hollowdark calendar is 12 × 30-day months + a 5-day year-end
festival = 365 days, with 7-day weeks (docs/01-world.md and the
style bible). Months live in canonical order:
spring 1 Thawing 2 Greening 3 Blossomtide
summer 4 Highsun 5 Amberhaze 6 Harvestmark
autumn 7 Firstfall 8 Stormturn 9 Ashfall
winter 10 Rainfall 11 Hollowdark 12 Rimefrost
festival 13 Year's End Festival (5 days)
time/calendar.ts month names, season mapping, days-per-month,
festival helpers
time/gameTime.ts GameTime shape + arithmetic: makeGameTime,
addDays / addWeeks / addMonths / addYears,
daysBetween / weeksBetween, compare / isBefore /
isAfter / isSameDay, dayOfWeek, dayOfYear,
toAbsoluteDays, formatGameTime
time/granularity.ts LifeStage + TickUnit mapping — one tick is a
year in infancy, a season in childhood, a month
in adolescence and old age, a week in adult life
(docs/05-time-system.md, ARCHITECTURE.md §5)
time/speed.ts Speed type: 'paused' | 'play' | 'fast'
time/index.ts public re-exports
Arithmetic is implemented by flattening GameTime to absolute-day
integers (year × 365 + dayOfYear - 1) so addDays, daysBetween, and
ordering are exact integer math. addMonths uses 13-month modular
arithmetic and clamps the day into the festival's 5-day length on
overflow.
59 unit tests in tests/unit/time/ cover constants, validation,
arithmetic edge cases (Rimefrost 30 → Festival 1, Festival 5 →
Thawing 1 of next year, day-clamping when landing in the festival),
negative offsets, tickOfDay preservation, day-of-week stability,
and life-stage boundaries.
|
|
rng/ is the load-bearing primitive for simulation determinism
(ARCHITECTURE.md §26). All gameplay randomness routes through
createRNG; Math.random is forbidden in gameplay code by ESLint.
rng/derive.ts xmur3 string hash + deriveSeed(parentSeed, label),
stable across processes and runs
rng/seeded.ts SeededRNG interface, mulberry32 implementation,
next / nextInt / nextBool / pick / weightedPick / sub
rng/index.ts public re-exports
Sub-RNG derivation is the key technical move: seeding a child with
hash(parent_seed + label) lets NPCs' trajectories be regenerated
deterministically from their identity tuple alone — which is how
Tier 3 NPCs stay off-disk until the player needs them.
29 determinism tests in tests/determinism/rng.test.ts cover: same
seed → same sequence, sub-RNG independence from parent consumption,
sub-order matters, input validation, and a byte-level inline
snapshot that locks the PRNG output for seed "hollowdark". The
snapshot is load-bearing — if mulberry32 or xmur3 changes, every
existing save diverges, so treat a snapshot break as a migration
concern, not an update-the-snapshot fix.
|
|
configure-pages@v5 with enablement: true fails on a fresh repo with
"Resource not accessible by integration" — creating a Pages site is
administration-scoped and the workflow's GITHUB_TOKEN only carries
pages: write, which is enough to deploy but not to provision.
The one-time manual step is: Settings → Pages → Source: GitHub Actions.
After that, this workflow is idempotent and deploys on every push.
|
|
configure-pages fails on first run when Pages hasn't been turned on in
the repo yet. Passing enablement: true lets the workflow provision the
Pages site using its own pages:write token — no manual Settings → Pages
trip needed, and subsequent runs are idempotent.
|
|
On push to main: install with frozen lockfile, type-check with
svelte-check, lint, then build with BASE_PATH set to /<repo-name>
so kit.paths.base produces correct asset URLs under the GH Pages
subdomain. Artifact uploaded via actions/upload-pages-artifact@v3
and deployed via actions/deploy-pages@v4.
Concurrency group 'pages' prevents overlapping deploys from
stepping on each other; cancel-in-progress left off so a queued
deploy from a later commit waits for the earlier one to finish
rather than aborting it mid-upload.
|
|
Determinism is load-bearing in Hollowdark (ARCHITECTURE.md §26).
Same seed plus same choices must produce bit-identical outcomes —
otherwise bug reproduction, lazy NPC backfill, and save integrity
all break. Every random call has to route through the seeded PRNG
in rng/.
Three restricted-syntax rules, scoped to gameplay directories via
an explicit files glob:
- Math.random() — forbidden
- crypto.getRandomValues() — forbidden for gameplay
- Date.now() — forbidden; use GameTime from time/
Each rule carries a specific error message pointing at the
architectural rationale. The restrictions lift inside tests/,
scripts/, and *.{test,spec}.ts where non-deterministic helpers
are fine.
@typescript-eslint/no-explicit-any set to error across the board.
|
|
The single source of truth for colors, fonts, spacing, type scale,
line height, and transition timing — applied as CSS custom properties
on :root per rules/01-code-style.md. Component styles reference these
via var(), never hex literals.
body.crisis-mode swaps --color-bg, --color-text, and --color-accent
to the tighter palette from docs/22-crisis-mode.md. The transition
runs at --transition-ceremonial so the shift is felt rather than
flashed.
Global reset: no-select everywhere (the reading surface is not a
document), .selectable escape hatch for input fields, smooth scroll,
minimal scrollbars hidden on coarse pointers, prefers-reduced-motion
respected. The oncontextmenu handler in app/app.html suppresses
right-click.
Font-face declarations point at static/fonts/ with font-display: swap
and relative URLs so they resolve correctly under the GitHub Pages
base path. Font files land separately.
|
|
Prettier: no semicolons, single quotes, no trailing commas, 100-column
body width, 80-column prose markdown. Svelte plugin registered for
component formatting.
ESLint is added in a separate commit since the lint rule forbidding
Math.random in gameplay code is its load-bearing purpose; the base
flat config appears there.
|
|
SvelteKit 2 with adapter-static, Svelte 5, TypeScript 6 strict mode.
Design decisions follow ARCHITECTURE.md §2 and technical/01-file-structure.md:
no src/ wrapper, implementation folders sit at the project root alongside
the design corpus in .claude/, and each top-level folder is its own import
alias (engine/, events/, flow/, scene/, etc.) configured in svelte.config.js.
Framework configuration routes SvelteKit away from the default src/ layout:
templates live in app/, hooks in hooks/, routes in routes/, assets in
static/. The favicon is a quiet amber dot on #13100E; .nojekyll keeps
GitHub Pages from stripping the _app/ build folder.
Deploy target is GitHub Pages. adapter-static uses fallback: '404.html'
(the GH Pages SPA convention) and kit.paths.base reads BASE_PATH from
the environment so dev serves at the root while the deploy workflow
builds under /<repo>/.
Prerender tolerates missing /fonts/ files so the build doesn't block
until font files land — see static/css/app.css @font-face block.
Licensed MIT.
|