ludic/packages/ludic.tracks
Orkuncakilkaya 71735b10a2 feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune
ludic.base's Queue<T> carries a QueueTag (its name and pending count): queue_new(name), q_push(q, v),
q_drain(q), q_clear(q) take no BaseState, and core_undrained(tags) names the given queues still holding
facts. A reducer on a package's state can now call that package's verbs (wallet_earn(wallet_st, n)).

ludic migrate state --prune (ludicc --migrate-prune) takes out each state parameter a function no
longer uses, nor anything it calls, and the argument that fills it - including an argument for a
parameter the callee has dropped, which is taken out before the call is checked, so a generic's T is
told by the argument that says it. A reducer keeps its state. --dry-run now counts the edits it would
make. Every package was moved with it: 608 base_st parameters and their arguments, 1889 edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 04:19:56 +03:00
..
tests feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune 2026-09-26 04:19:56 +03:00
config.ludic wip(0.S3): packages and examples migrated again from their pre-0.S sources in one run 2026-09-25 15:31:34 +03:00
index.ludic refactor(packages): the mechanic packages use ludic_base only, and ask through ports 2026-09-25 05:36:11 +03:00
leave.ludic wip(0.S3): packages and examples migrated again from their pre-0.S sources in one run 2026-09-25 15:31:34 +03:00
package.ludic feat(ludic.tracks): prints, their age and reading them as a mechanic package 2026-09-25 04:54:43 +03:00
queries.ludic wip(0.S3): packages and examples migrated again from their pre-0.S sources in one run 2026-09-25 15:31:34 +03:00
read.ludic feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune 2026-09-26 04:19:56 +03:00
README.md refactor(packages): the mechanic packages use ludic_base only, and ask through ports 2026-09-25 05:36:11 +03:00
state.ludic feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune 2026-09-26 04:19:56 +03:00
system.ludic feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune 2026-09-26 04:19:56 +03:00

ludic.tracks

What animals leave on the ground: prints in a ring per kind, each remembering who left it, which way it went and when, so a print can be read up close ("fresh elk tracks, heading north-west"). Other sign - droppings, a legend's mark - is told to the package so every reading goes through one queue and one count. Uses ludic.base and nothing else.

import "ludic.tracks"

The package is the logic only. A kind is the game's number (a hoof, a paw); the game draws the prints through the placed hook, which hands it the kind and the ring index, and the ring index is stable until the ring comes round and writes over it.

A print is read ONCE: reading it again is still a TrackRead fact (the game says what it is again) but first is false and it counts nothing, so pressing a key over one print is not as good as walking a trail.

The port

export port TracksWorld {        # every member has a default, so any may be left out
  now: fn() -> float              # game hours counted from the start of the trip (unbound: 0)
  skill: fn() -> int              # the reader's tier; at tracks_config_reading's name_at it names the animal
  bearing: fn(float) -> string    # a yaw in words, "" for none (a reader with no compass)
  placed: fn(int, int) -> void    # a print was put down at (kind, ring index): draw it
}

The game fills it once, where it is put together - a declaration, not a call:

bind TracksWorld { now: fn rt_trip_hours, skill: fn track_skill, bearing: fn track_bearing, placed: fn track_mark_placed }

API

bind TracksWorld { ... }, tracks_config(kinds, cap), tracks_config_reading(name_at, fresh, recent) the port; how many kinds and how many prints of each; the skill that names the animal and the ages (hours) under which a print is fresh or recent
tracks_leave(kind, x, z, yaw, sp, legend) -> int, tracks_leave_at(..., made) a print, now or at another machine's time; returns its ring index
tracks_clear() the ground forgets every print (a new world); the reader keeps what it learned
tracks_nearest(x, z, r) -> int the nearest print within r, as a slot (kind * cap + index), or -1
tracks_read(slot) read a print: a fact every time, a count and the last bearing
tracks_note(sp, source, legend, yaw, first) a sign that is not a print (TRACK_SIGN, TRACK_SCAT); the caller knows whether it is its first reading
tracks_note_bearing(yaw), tracks_set_read_count(n) the way the last thing went (a bed); the count outright (a test, an old save)
tracks_kinds(), tracks_cap(), tracks_count(kind), tracks_slot(kind, i) the rings
tracks_x, tracks_z, tracks_yaw, tracks_sp, tracks_legend, tracks_made, tracks_age, tracks_was_read (slot) a print
tracks_fresh_of(age) TRACK_FRESH, TRACK_RECENT or TRACK_OLD
tracks_read_count(), tracks_last_yaw(), tracks_has_bearing() what the reader has learned
tracks_reads() -> Queue<TrackRead> the facts: { sp, source, kind, legend, age, fresh, yaw, named, bearing, first }
tracks_system() -> System "tracks", PH_SIMULATE: reset, and its save section (the count and the last bearing; the prints are the ground's and are not saved)

Tests

ludic build packages/ludic.tracks/tests/tracks_test.ludic --headless -o /tmp/tracks_test && /tmp/tracks_test