Commit Graph

61 Commits

Author SHA1 Message Date
Bulat Fatykh 0484c02517 Turn a non-finite GSC number into a script error, not a panic, when it meets text
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:28:56 +03:00
vtroc7 11514ea37d Keep relayed matches through short link loss; stop browser polling in a lobby
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:23:24 +03:00
vtroc7 03016c48d6 Script-driven post effects, fog and earthquakes; equipment recovery, akimbo viewmodels, end-game leave and scope edges
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:01:29 +03:00
awsms 23f6c1aacc Fix retail settings menu startup and pause responses after class changes (#17)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 20:01:29 +03:00
Bulat Fatykh 77aafbf1cd Remove the direct UDP mode (#20)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:54:24 +03:00
Bulat Fatykh 6e0e184b07 Name each pm_flags bit once, in playerstate_iw4::pm_flags (#19)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:54:23 +03:00
Bulat Fatykh bbdfb7c1b6 One owner for the alpha-test table and three shared constants; drop the unread GameEnded (#15)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:54:22 +03:00
Bulat Fatykh 67c46d022e Validate a master client's address before it holds a connection slot (#14)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 19:54:21 +03:00
vtroc7 f66380176c Merge integrated pull requests #10, #11 and #12 2026-09-30 08:34:26 +03:00
vtroc7 56c6d8ebcb Improve equipment, missile guidance, animations and weapon audio
Remove unused weapon summaries and catalog APIs, share model geometry helpers, and consolidate per-frame renderer census resets.
2026-09-30 08:30:12 +03:00
Bulat Fatykh b7f04c53f3 Remove unused runtime code and dependencies 2026-09-30 07:53:22 +03:00
Bulat Fatykh b36dcc96e9 Forget the menu's game mode once the session is back in the menu
The mode picked in the lobby outlived its match: HostMatchRules was dropped on the way back to the menu, HostGameModeSelection was not, so a later `map` from the console started the old mode with default rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:53:04 +03:00
vtroc7 41115ebf53 Keep mixed-device input seamless and stabilize depth-equal rendering 2026-09-30 07:49:50 +03:00
chazmm f1f8c0d554 Add controller support: button layouts, stick aim and aim assist
Controller buttons bind through the same table as keys: every command on
the controls page shows its key and its button, and either is rebound by
pressing a key or a button. The options gain a Controller page: button
layout (Default, Tactical, Lefty, Bumper Jumper, Bumper Jumper Tactical),
stick layout, look and aim-down-sight sensitivity, invert, response curve,
aim assist, vibration and deadzones, all kept in settings.cfg.

The look stick turns at rates that build up to full deflection, slower
down the sight and scaled by the zoom. Aim assist works over visible
enemies within each weapon's own aim assist ranges, read from the weapon
data: slowdown and lock-on, and on Full a pull onto a target when the
sight comes up. It applies only while aiming with the controller.

Menus take the D-pad or left stick, A and B, and Start opens them. Focus
moves by screen position rather than by menu file order, which jumped
around the options pages for arrow keys too; left and right on a button
cross columns. The Advanced Video page no longer draws the FOV slider
over Bloom.

The controller in use is the last one pressed or moved, so a virtual
joystick or an idle second pad stays out of the way; rumble follows it and
the vibration setting. Use/reload reloads when nothing usable is in reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 04:41:49 +03:00
vtroc7 60f550a2b7 Support personal loadouts and fix lobby and menu interactions 2026-09-30 04:38:21 +03:00
Bulat Fatykh 7a04f2c450 Drop 48 dependencies no crate uses
render -> naga stays: its wgsl-in feature may be pinned on purpose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:52:44 +03:00
Bulat Fatykh 5fb32a3a0b Keep one content digest
content_digest_v0 and _v1 had no callers; the live digest and components lose their version suffix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:52:44 +03:00
Bulat Fatykh 4280f8f2be Drop the unread weapon-state inputs of the first-person view
auth_ws had no users, map_authority_to_fpv ignored both weapon states, and last_weaponstate / last_sprinting were written and never read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:52:44 +03:00
Bulat Fatykh 428abee54d Remove render_frontend's copies of smodel_cached and gpu_prepare
render_gpu owns both; the frontend copies had no users.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:52:44 +03:00
Bulat Fatykh 938c706e9a Remove fastfile_iw4::shader
A second SM3 bytecode parser that nothing imports; d3d9_sm3 is the one in use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:52:44 +03:00
Bulat Fatykh 2065316344 Bound IW4 wire offsets by MAX_XFILE_COUNT
Same bound as before (8), named the way fastfile_iw5 names it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:52:44 +03:00
Bulat Fatykh a7a3fd596f Forget the menu's game mode once the session is back in the menu
The mode picked in the lobby outlived its match: HostMatchRules was dropped on the way back to the menu, HostGameModeSelection was not, so a later `map` from the console started the old mode with default rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 01:52:02 +03:00
chazmm f2fbe816f7 Add controller support: button layouts, stick aim and aim assist
Controller buttons bind through the same table as keys: every command on
the controls page shows its key and its button, and either is rebound by
pressing a key or a button. The options gain a Controller page: button
layout (Default, Tactical, Lefty, Bumper Jumper, Bumper Jumper Tactical),
stick layout, look and aim-down-sight sensitivity, invert, response curve,
aim assist, vibration and deadzones, all kept in settings.cfg.

The look stick turns at rates that build up to full deflection, slower
down the sight and scaled by the zoom. Aim assist works over visible
enemies within each weapon's own aim assist ranges, read from the weapon
data: slowdown and lock-on, and on Full a pull onto a target when the
sight comes up. It applies only while aiming with the controller.

Menus take the D-pad or left stick, A and B, and Start opens them. Focus
moves by screen position rather than by menu file order, which jumped
around the options pages for arrow keys too; left and right on a button
cross columns. The Advanced Video page no longer draws the FOV slider
over Bloom.

The controller in use is the last one pressed or moved, so a virtual
joystick or an idle second pad stays out of the way; rumble follows it and
the vibration setting. Use/reload reloads when nothing usable is in reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 14:04:22 +01:00
vtroc7 1a0daffd18 Play T5 and IW5 maps under IW4 match rules 2026-09-28 20:48:06 +03:00
vtroc7 38aa9cf741 Run IW4 match flow, menus and killstreaks from the game scripts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 18:35:44 +03:00
chazmm 7af11e5500 Roughly double no-bot frame rate by cutting per-frame CPU work
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 18:35:44 +03:00
awsms 665d4ff6df Hold aim alongside toggle aim; mouse wheel directions in key bindings
Controls list Aim Down the Sight and Hold Aim Down the Sight; pressing hold
clears a toggle latch so release always leaves ADS. MWHEELUP and MWHEELDOWN
bind from the menu, the console and saved settings, and wheel scroll reaches
the game as discrete presses, so weapon cycling works on the wheel.

Closes #6
Closes #7
2026-09-28 17:51:18 +03:00
vc a6fa0b9514 Merge pull request #4 from chasmlol/lol/fix-high-fps-sprint
Fix sprint cancellation at high frame rates
2026-09-25 23:46:26 +03:00
vc 4aec953b32 Merge pull request #5 from chasmlol/lol/video-fov-slider
Add an FOV slider to video options
2026-09-25 23:45:34 +03:00
chazmm 070c3c37ff Add a persistent FOV slider to video options 2026-09-25 19:38:53 +01:00
chazmm a484cbf13a Fix sprint cancellation from high-FPS movement sampling 2026-09-25 19:38:10 +01:00
vc c6c26b8885 Merge pull request #2 from LuaxY/macos
Run on macOS: create the Metal surface on the main thread
2026-09-25 18:32:56 +03:00
Luax b271b4d0a5 Run on macOS: the Metal surface is created on the main thread
- Pipelined rendering is off by default on macOS. The single-threaded Render
  schedule runs `create_surfaces` on the render thread, and AppKit only lets
  the main thread touch the NSView behind the Metal surface, so the first
  frame panicked in raw-window-metal. IW4L_PIPELINED_RENDERING still
  overrides it.
- docs/BUILD.md: macOS needs only the Xcode command line tools; game data is
  the Windows depot of a Steam copy fetched with steamcmd.
- README and docs index list macOS (Metal) among the platforms.
2026-09-25 21:59:09 +10:00
vtroc7 72ee2098da Improve killcams, combat effects, and asset presentation 2026-09-25 00:05:20 +03:00
vtroc7 73ea661667 Add killstreaks and fix collision, materials, and HUD 2026-09-24 18:54:31 +03:00
vtroc7 69cf9ff9fa Prepare weapon presentation and add alternate modes, device feedback and collision fixes 2026-09-24 05:30:17 +03:00
vtroc7 f357ef16be IW5 attachments and weapon overrides, a blood film on the HUD, and team-tinted obituaries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 23:45:43 +03:00
vtroc7 94e3983bfc Shared common content, prepared weapon families, and one way out of a session
- One common set per content profile, shared by every match and the loadout
  menu; a restart over the same map keeps its images and static geometry.
- Weapons resolve as families: attachments are chosen from each game's own
  tables, checked, and composed into prepared configurations with their
  first-person skeleton assembled once.
- Sessions end through one teardown and one process exit; retired resources
  are dropped on a worker, not the frame.
- Sound is captured by the walks that already read each zone.
- A loading screen that reports what the load is doing.
- Tests live only in crates/approved_tests (heavy_gameplay_lifecycle);
  publish-check refuses them anywhere else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 17:10:02 +03:00
vtroc7 52d31f1ee2 A loading screen that says what the load is doing, and a first-person rig prepared once per composition
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 23:28:55 +03:00
vtroc7 b7e00544bb Destructible props, a live scoreboard, and film vision on the console
Destructibles walk their stage staircase on the host: each stage carries
its model, the parts it throws off, the effects it fires on the way out
and the health it bleeds afterwards, and the loops a prop is speaking are
published as sound aliases the client resolves through the configstring.

The scoreboard, overhead names and the compass draw against the live
roster -- team identity survives player-supplied color escapes, ranks and
prestige come out of the menu tables, and the final snapshot is kept when
it lands ahead of the next scores command.

Film vision presets load from the map and common fastfiles; visionSetNaked
and visionReset transition between them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 14:41:01 +03:00
vtroc7 bca31343e5 Bots that fight and take objectives, and glass that cracks into real shards
Host bots bake a navigation graph off the clip map and walk it — drop and
break-glass edges, routes that resume across ticks under a shared per-tick
query budget, firing positions chosen from baked supports rather than a ring
around the bot. They pick a weapon, reload or switch when the magazine runs
out, and plant, defuse and claim flags on their own.

A broken pane now builds a planar crack graph and emits shards carrying their
holes and open cracks, so tessellation and repeated hits read the stored
geometry instead of assuming a convex fan from vertex zero.

Sun cascades are clipped to the view. Class select and the options menu match
the layout players expect. Weapon definitions load from IW5 and T5 fastfiles.

The test suite is removed: it had grown into scaffolding for the dig rather
than a description of the runtime, and new tests will be written against the
behaviour that stayed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 02:49:55 +03:00
vtroc 2fb3e4558b Decode on the loader's own threads and only what is still claimed, and time the age of the presented state.
Image decode takes the pool it runs on as a parameter and every lane hands it
the loader's. `ComputeTaskPool` is the frame's pool, with its own thread count
and its own affinity, so a load stage that decoded on it ran on the frame's
threads and competed with the frame for them. Material compile chunks move onto
the same load pool instead of raw `std::thread::scope` threads: the pool's
workers set their affinity to the process CPUs when they spawn, while a thread
spawned from an `AsyncComputeTaskPool` worker inherits that pool's narrower set.
The compile scope is explicitly non-cooperative, so the calling thread waits for
its chunks rather than ticking the pool's queue.

The `common_mp` FPV plan is held rather than enqueued, and decodes once the
donor plans have merged. `prune_to` drops every demand whose rows the merge
would not fill — the same predicate `apply` uses, so pruning changes when the
question is asked and not what the catalog ends up holding — and
`release_claims` hands the rows back when nothing survives, which is otherwise
`apply`'s job and would leave later stages skipping every name the plan had
claimed. The census counts `pruned_variants` and `pruned_rows` on their own,
apart from duplicate claims and from discards, and the report line carries what
was cut, what is left and what asking cost.

`presented_state_age` and `presented_frames_behind` measure from a render
frame's extract to the render graph's Finish set: how old the state a frame drew
was, and how many main frames had opened since it was taken. They get their own
table in the counter report, because an age divided by a frame is not a share of
one, and a line in the frames section beside main/render overlap — what
pipelining costs, next to what it buys. Both say where they stop: bevy presents
after the graph schedule returns, so the present, the compositor and the panel
are outside, and neither is an input-to-photon latency.

`IW4L_FRAME_LATENCY` sets the surface's `desired_maximum_frame_latency` without
a rebuild. The backend may clamp it, so the manifest records the number asked
for and never a grant this process cannot read back. Env switches now read
through one rule in `perf::switch`: absent, empty, `0`, `false` and `off` are
off. `IW4L_MULTI_DRAW=0` therefore turns the indirect path off instead of
enabling it, which is what a paired run across it needs.

A lightmap page counts the bytes of all six of its images in `handed_bytes`,
`bytes_total` and against the budget pacing the overlay frames, and a slot that
takes another slot's handle no longer counts as having handed anything over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 00:44:05 +03:00
vtroc 9856f91a98 Share one decode between askers, and time each frame from its own wall.
The split of an image variant into `payload` and `usage` is now what sharing
keys on. A registry holds the texels an archive entry decodes to — the mip
chain in one allocation, laid out as an upload wants it — and the `Image` is
built over them where the row is placed. Two materials that want the same file
under a different colour space or sampler cost one read, one inflate and one
decode, and the second of them a copy of the chain; the last asker copies
nothing, because the decode's own buffer becomes the image's. Asking whether a
name is a cubemap reads the thirty-two byte IWI header out of the entry instead
of inflating the megabyte behind it.

The resident-bytes ceiling is charged per payload rather than per batch, so two
batches pointing at one buffer hold one allocation between them. Load rows
split `produced_bytes` into `prepared_bytes` and `reused_bytes`: only the first
is work, and their sum is what the plan served. The merge census names who won
a disputed claim by plan id, not only that somebody did.

The frame clock closes the row and opens the next at the top of `First`, before
anything else runs, so every main-phase interval falls wholly inside one frame;
the frame the process exits from is closed at exit and its row carries
`partial`. Which spans are a frame's coverage roots is declared by
`Span::coverage_root` rather than read off a thread's stack — an executor is
free to run a schedule's `begin` and its `end` on two workers — and spans a
migration left behind are pruned. A counter measured on another thread is
charged to the frame it ran in through `emit_at`, and a sample that finds no
row is counted rather than moved to whichever frame is open.

`IW4L_PIPELINED_RENDERING=1` runs the render world a frame behind on its own
thread, and it is the only thing the switch moves; the manifest records which
branch a run took. The report gains `main/render overlap`, read off the
intervals rather than off the frame a render stage names as its origin. A
`bevy-trace` build writes bevy's own spans as a Chrome trace, which is the only
place the render graph's submit can be timed from, and
`scheduling.bevy_tracing` says whether a run was one.

Glass keeps its shattered material while damaged, scales the authored UV to it,
picks its shatter effect by whether the piece is landing, and carries the
surface normal on the presentation event.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 16:28:01 +03:00
vtroc 3da14468fd Keep the two-bot Domination clip off the default test run.
`mp_boneyard_dom_and_two_bots` ends with the flag still mid-progress: one bot
holds the capture alone while the other reports `BudgetExhausted` on its path,
and the 500 ticks the test allows run out before the flag turns over. It fails
on every run and takes about a minute of the suite to do it.

It is `#[ignore]`d rather than deleted — the walk, the sight and the fight it
covers are real, and the run is one `--ignored` away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:05:37 +03:00
vtroc d00bf2309a Separate the bytes an image decodes to from the wrapping around them.
An image variant's identity was one digest of the archive entries plus a bag
of option bits, which mixed two unlike things. The map type chooses between
the 2D and the cubemap decode, so it changes the bytes; the colour space and
the sampler state change no byte of the payload, only the `Image` built
around it.

The identity now splits along that line: `payload` digests the resolved
entries and the map type, `usage` carries the colour space and the sampler.
Sharing still keys on the whole variant, because the wrapping lives inside
the `Image` whose buffer would be shared.

The merge census reads the split. A discarded claim whose winner held the
same payload under a different wrapping was counted as overridden by another
source, which reported nothing to share where the same archive entry had in
fact been read into the same bytes twice. It is its own bucket now,
`discarded_same_payload`, on the discard line and in the image demand report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 13:01:20 +03:00
vtroc 1b7888539a Write the public-push pass into the maintainer contract.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 22:32:25 +03:00
vtroc c72783395d Charge a span to the frame it ran in, and prepare an image variant once.
A 47.78 ms frame reported a 36.35 ms `Load` inside an `Update` its own row
implied was 11.87 ms, and 364 frames summed their root spans to more than their
own wall. Three mistakes stacked: a span was credited to whichever frame was
open when it *closed*, its parent was whichever span had opened most recently
anywhere in the process, and the remainder was a sum of overlapping intervals
clamped at zero.

Now a span is charged by intersection with the frame's wall, and what it ran
before the frame opened is `carried_in_ns` rather than nothing. Nesting comes
from a thread-local stack, so the render thread no longer adopts the main
thread's spans, and a close out of order is counted. `unclassified` is wall
minus the union of the root intervals, which cannot go negative and counts two
threads inside one nanosecond once. The report prints how many frames still
hold a span longer than its parent.

`present_hud_surfaces` and `present_hud_stage_max` measured wall *between*
systems, which `.chain()` leaves the executor free to fill. They are renamed
`*_schedule_interval`, tabled apart, and given a number that is the HUD's:
`hud_tess_body`, taken inside the tess flush bodies, with `hud_tess_jobs`
beside it. The screenshot flag now stays up through the readback, the encode
and the write instead of only while the request queues.

An image decode gains an identity — the resolved IWD candidate list plus the
recipe bits that change the payload — so two plans that would prepare the same
bytes share one decode through a registry of weak handles, and a claim the
merge drops is reported as the same variant twice or as another game's
different image under the same name. Merge precedence and the final content are
untouched. The load job row splits budget wait from decode, renames
`join_wait_ms` to what it measures, and carries `produced_bytes`; the budget
wait is bounded so it cannot outlive a join order that stops guaranteeing
progress. The manifest walks the nested artifact cache instead of reading zero
files from its top directory, and prints the viewport the passes were sized by
beside the surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 21:19:25 +03:00
vtroc 2f71d93b3a Package what each run measured, and decode the match's audio once.
`make bench` printed two reports and left nothing behind but a text file, so a
number could be compared with another number from the same afternoon and not
much else. It now prints three and writes the run's own directory: `report.txt`,
`manifest.json` and `summary.json` beside the `trace.pftrace`.

Section [3/3] is the counters — the render stages, the GPU passes, and the
per-frame work census. A counter nobody sampled prints MISS rather than
`0.00`, and the counters and spans this build declares but this run never
reached are named at the end, so a hole in the instrumentation cannot read as a
fact about the workload. GPU passes overlap and resolve late, so they are never
summed.

`manifest.json` says what produced the number: the revision, the dirty flag,
the toolchain, the profile cargo actually built under, the locked `bevy` and
`wgpu`, the adapter and whether its device can time a pass, the window, the
pools, the env toggles and the cache state. The build half is baked in at
compile time, because asking `git` at runtime names the tree the process is
standing in rather than the build. A fact nobody could read is `null`, never a
plausible default.

Reading that report, `preparing match audio` was the largest stage of every
load — one row holding one number, which cannot tell a thousand cheap byte
loops from five hundred external processes. It is the second. Section [1/3] now
splits the stage by the decoder each clip took, in worker time, and counts what
each one prepared, failed, held and left resident.

The T5 XWMA decode goes through the artifact cache under `xwma_pcm`, keyed on
every input the decode reads, so a clip is decoded once per machine rather than
once per launch. A cold load still had to pay the first time: 515 `ffmpeg`
processes, 18.9 s of worker time, almost none of it decoding. A worker now
takes the jobs already queued and answers up to 64 clips through one process —
every clip its own input and its own output, nothing concatenated — which is
1.3 s for the same bytes. Failure stays per clip: a batch `ffmpeg` refuses is
re-asked one clip at a time, leaving the bad clip as the only failure.

`make bench-overhead` can point at either recorder, `VAR=IW4L_PERF` or
`VAR=IW4L_BENCH`; they are separate costs and each needs its own pairs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 19:43:47 +03:00
vtroc 311c8fa336 Time a whole run in one process, and keep a hitch from ending the session.
`make bench` is one recipe over one run now. `IW4L_BENCH=1` arms an in-process
recorder on the spans that already feed Perfetto, and at exit the run prints
two independent reports and writes them to `iw4l-artifacts/bench/<stamp>.txt`:
the waterfall from the shell command to a playable map, carrying the time each
load stage held the pool alone, and frame time as a span tree whose nesting is
recorded rather than declared. Neither needs a trace file or Trace Processor,
and either can be MISS while the other stands. `bench-demo` and `bench-load`
are gone — a goal after `bench` is a demo, `ZONE=` is a live map.

A bot is no longer minted onto an id a real client already owns. The roster
steps over the ids the authority and the local player hold, and a slot that
still lands on the local player is retired, because `is_bot` would otherwise
claim the player and nothing could drive the slot anyway.

A stretch with no authority ack re-adopts the next snapshot and counts one
stall; three of them still fail the link. Failing on the first turned every
long frame into a permanent disconnect.

The loading overlay comes down once the world is installed rather than waiting
on the class-select hop, which a replay never makes — it was playing the whole
demo behind `UiLayer::Loading`, with map ambience held back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013bHwyKwUjaYmtVG1F3Ttku
2026-09-15 14:56:07 +03:00
vtroc 91133418ea Say the project has no releases instead of describing ones that never shipped.
The release section documented a tagging and notes procedure that has produced
nothing: no GitHub releases exist and the only tag is pre-ship-1818. What it
said that was true and lived nowhere else — unstable wire protocol, playtests
among people who arranged them, nothing phones home — becomes a short Status
block up top. The rest already lives in docs/DEPLOY.md, docs/MASTER.md and
docs/WINDOWS.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkgiSVEPTbsVZ1RpARujF6
2026-09-15 02:09:29 +03:00