Skip to content

Releases

tl 4.0.0-preview.1

tl 4.0.0-preview.1 Pre-release
Pre-release

Choose a tag to compare

@IAFahim IAFahim released this 06 Nov 10:48

Pre-release of the 4.0 line with kinds. Not published to NuGet yet; the owner publishes from the publish-nuget workflow or dotnet nuget push.

Kinds: authored kind layouts baked by the new tlb kinds command, plus the K0 kind-system benchmarks. See docs/KINDS.md.

Packages (version 4.0.0-preview.1): Tl.Runtime, Tl.Gen.CSharp, Tl.CSharp, Tl.Bake (tlb, the tool that has kinds; the published Tl.Bake 2.0.1 does not).

Verified: .NET 10 (886 tests pass, kind-system benchmark gate met on the dev machine) and Unity 7000.0.0a16 through tl.unity (EditMode 36/36, PlayMode 30/30).

Unity: install the package with https://github.com/IAFahim/tl.unity.git?path=/com.tl.unity#v0.2.0-preview.1. Bake with tlb built from this tag (dotnet build tools/Tl.Bake -c Release) and set TL_TLB to it.

Known gaps: P6 clip TimelineId reachability; CI runner pending; Unity 6000.x unverified; release performance gates are not all decided on the release machine, so this stays a pre-release.

1.2.0

Choose a tag to compare

@IAFahim IAFahim released this 24 Sep 20:23
bc05420

What's new

Wider consumer surface (#356/#357) — the settled 10/30 contract:

  • OnMemo folds up to 10 results per consumer with element widths 1/2/4/8 bytes (8-byte results fold into adjacent 4-byte lane pairs).
  • OnActive takes up to 30 live gameplay parameters (memo feeds included), bound by TypeKey through the caller-assembled ColumnSet (Add<T>(span) cold assembly; warm read is the direct pointer row).
  • Same-type duplicate columns are rejected at generation (TLGEN81); TLGEN68's bound message now names 30. The 40-slot consumer slot row and the byte-identical warm path are receipted.

Run-aware cascade (#351/#358) — PMU counter harness (perf stat/perf record protocol documented) and run-aware routing for run-heavy mixed chunks: −31% cycles/row on block shapes, scalar bands unmoved, 0 B warm. Cycle counters are the perf baseline of record (ns/row platform drift documented on the issue).

ApplyLive frame sized to composition (#362) — the live-compose stackalloc drops from the 45,544 B worst case to the call's actual composition (canonical 2-consumer shape: 1,765 B, −96%; true in-contract worst case ≈ 25 kB). Stateless sizing (no install-invalidation hazard), 0 heap, warm row loop untouched.

Smaller public surface (#361) — 8 dead Timeline<TTrack,TClip> Apply/Advance rungs removed (24 → 16); every surviving rung byte-identical on the hot path (reviewer token-diff + interleaved A/B within ±0.05).

Repo hygiene (#353/#355, #359/#360, #363) — Inspect Code record 972 → 280 findings with strictness fixes; the analyzer now sees the whole repository including the Playground (Blazor WASM) with a cold-sweep protocol; a live-frame Timer callback bug in the Playground was fixed; deferred internal naming renames executed with zero API change.

Budget — production source 299,978 → 294,361 B under the 300,000 cap.

Migration (pre-1.0 policy: direct removal, no shims)

  • Removed public rungs (#361): dispatch single/scalar (Apply(ReadOnlySpan<ushort>, ReadOnlySpan<ushort>, bool), Apply(ushort, ReadOnlySpan<ushort>, bool), Apply(ushort, ushort, bool), generic Apply<TIndex,TPosition>(ref, ref, bool)), range batch/scalar (Apply(ros, ros, ros, bool), Apply(ushort, ushort, ushort, bool)), asset dispatch (Apply(TimelineAsset, ros, bool)), and asset-fused (Apply(TimelineAsset, ros, Span<ushort>, bool, Span<float>)). Callers re-point to the surviving generic dispatch/fused rungs — identical bodies; e.g. asset callers pass asset.Index, scalar-dispatch callers pass a 1-element span.
  • Consumer bounds (#356): OnMemo result cap 4 → 10 (widths 1/2/4/8), OnActive parameter cap 4 → 30, new ColumnSet for multiple distinct in/ref types; same-type duplicate columns are now a generation error (TLGEN81).
  • tlb --json: output fields widened where consumers carry the larger surface (carry-forward from earlier alphas).
  • ApplyLive (#362): the generator-authored surface is unchanged; hand-written thunks outside the generated contract fail fast with located throws instead of running with oversized frames.

Validation

Tag v1.2.0 at commit bc05420. Release-branch gate: suite 1,985/0, checked 1,092/0, NativeAOT linux-x64 exit 0, capacity checksum −4004417433036324864 exact, samples/Mixed byte-identical, eng/release-artifacts --tag v1.2.0 all-OK (tag/commit/version agreement asserted); publish-nuget validate + publish green; packages verified live on nuget.org (flat-container); quick-start pin lane green post-publish; playground redeployed from the merge push. Speed receipts on the issues (cycle counters; 0 B warm on every shape).

SHA256SUMS covers every asset below.

1.1.0

Choose a tag to compare

@IAFahim IAFahim released this 21 Sep 19:51
696b836

What's new

Two-method consumer contract (#340) — consumer lanes are now named methods instead of invisible overload shapes:

  • OnMemo(in Frame, out/ref results...) — runs once per tick at first use, frozen into per-result tables. N unmanaged outputs, each its own lane in declaration order. in is rejected (TLGEN76: it runs at fold — no caller); string and managed types are illegal outputs.
  • OnActive — three live shapes: dispatch (in Frame), framed live columns (in Frame, in/ref), and memo-compose (in arc, ref y, in multiplier) fed by the new column-carrying Apply(ids, positions, forward, effects, input). Per-row live inputs change every frame. out is malformed on OnActive (TLGEN78).
  • Legacy OnActive(in Frame, ref float) keeps working as single-result OnMemo sugar — byte-identical emission, bit-identical playback (full-corpus checksum equality).
  • The two-output memo Apply from this cycle ships as ApplyLanes (finalized before first publication; no published surface changed).

Teleport range Apply (#332/#334) — Apply(indices, from, to, forward) fires every crossed clip window in one call; wrap and clamp match Advance by construction; pair-isolated.

Timeline.BakeRef (#333/#335) — BakeRef(id, ref arg) arities 1-4: the writable-argument spelling for marker bakes. Plain Bake(id, arg) unchanged. (C# forbids both spellings on one name — CS0663.)

Fix: same-type Bake arguments (#337/#341) — repeated-type bake parameters now bind distinct arguments positionally (previously every parameter of a type received the first argument). Documented in the README bakes paragraph.

TL_CHECKED domain diagnostic (#339/#342) — out-of-domain runtime positions fail with a located error naming row, asset, position, duration (checked builds only; Release unchanged).

Docs — consumer-shape purity contract and cold-fold labeling (#336/#343); two-method authoring teaching with the multiplier example (#347).

Validation

Tag v1.1.0 at commit 696b836. publish-nuget validate (full gate) + publish green; quick-start pin lane green post-publish; suite 1,985/0, checked arm 1,107/1,107, NativeAOT exit 0, src budget 299,978/300,000; speed A/B vs 1.0.0-era main flat-to-faster on every shape, 0 B warm; playground redeployed. Known follow-ups: #348 (live-surface hardening).

SHA256SUMS covers every asset below.

v1.0.0

Choose a tag to compare

@IAFahim IAFahim released this 21 Sep 01:57
dfa8004

tl 1.0.0 — first complete stable release

API settlement with no compatibility aliases: the consumer method Execute is now OnActive, Step is now Advance, and the typed Apply/Advance overloads live on the Timeline<TTrack,TClip> pair surface (the Lane facade is removed).

  • Pair-isolated fold (#325): Timeline<TTrack,TClip> measures and plays only its own pair — a pair's fold never runs another pair's consumers, and one pair's columns never contaminate another's tables.
  • No-output consumers (#325): OnActive(in frame) with no gameplay parameters registers a dispatch-only consumer — it never runs at fold and fires per row at the position's tick through Apply(indices, positions, forward).
  • Playback floors receipted on every host shape and CPU tier: shared-clock crowds 0.08 ns/row, per-entity calls 0.80 ns forward, non-AVX2 tiers 0.48 ns via the kernel tier ladder and flat record arena — bit-exact across AVX2, Vector128, and scalar, 0 B warm allocation.
  • Bakes dispatch by discovered signature through the IBake marker; the live playground handles arbitrary valid authoring.

Migration from 1.0.0-alpha.x: no API change from alpha.10 — version pins only.

Packages

package version
Tl.Runtime 1.0.0
Tl.CSharp 1.0.0
Tl.Gen.CSharp 1.0.0
Tl.Bake 1.0.0

SHA256SUMS

57101400d4cf28cd79335e8135599d1befa87bdac72db7499602976cd7b9f360  RELEASE-MANIFEST.json
b33fb0bad091a70b19d6362e0251ee8cbded2b746f913e94e0b522ea4a08f120  Tl.Bake.1.0.0.nupkg
73896ee3963466ffc2b61ea93bf0927f630c571c4639b0c9e4570cbb03950ada  Tl.CSharp.1.0.0.nupkg
07995a140012a1f1575f3d8b5256ad3a60d193b60c8e801726dff7358f488a57  Tl.Gen.CSharp.1.0.0.nupkg
d989fc95642238ad54d278c5373a76ee4af073f50eebc3c8c8407fd492e4d32b  Tl.PackageConsumer-linux-x64.tar.gz
625dec26f515961f7e7051fbd572a7a7ae419ed937b75f6c0f54e707a3c903b2  Tl.PackageConsumer.generation-report.txt
8d7f8687cee54e0df6e9be0d09a34076ea50d07b2d57699fc74867b8f6585695  Tl.Runtime.1.0.0.nupkg
77b449ab5f8b14eee35686b18a2212f4cece0c38587ed098362c57a4b1530ea0  Tl.Runtime.1.0.0.snupkg

Tag v1.0.0 → dfa8004; artifacts built and published by the publish-nuget workflow (run 35552170392) from the exact tagged tree.

1.0.0-alpha.7

Choose a tag to compare

@IAFahim IAFahim released this 17 Sep 03:41

1.0.0-alpha.7

Performance and ABI release over the data-authored surface: the typed playback lane becomes the only playback path, the frame view slims to ushort positions, and the authoring toolchain fuses parse with bake.

Playback lane

  • Timeline<T>.Seek(positions, forward).Apply(effects) over BakedLane<TTrack,TClip>.Bind(asset) measured tables is the single warm path (issue #104, PR #106). The Timeline.Rows(...) facade, TimelineKernels catalog, TickUnmanaged walk, and tlbake --kernel are removed; there is one execution path and no interpreter tier.
  • Single-dispatch mixed loops and finite gather: 1M-row set numbers per shape — static staggered up to 22.9x over the alpha.6 facade lane, finite staggered 2x, mixed ids 1.3-1.4x; every shape bit-exact against per-asset static lanes, forward and backward, 0 B warm.
  • Catch-up is repeated single-frame calls; rewind is forward: false. There is deliberately no multi-frame step parameter.

Frame ABI (breaking, issue #113)

  • Timeline-domain positions and ticks are ushort under a single 65,535-tick cap; TLB1 load and tlbake reject larger durations with a diagnostic naming both numbers.
  • Cycle and GameTick are removed from the engine, Frame, TickFrame, TimelineState, TimelineComponent, and the consumer Execute/Range seam; Frame.ClipLength and Frame.WithinClip expose the slot window instead.
  • Movement records halve to 8 B, TimelineState 16 -> 8 B, TimelineComponent 24 -> 16 B, set tables 44 -> 28 B/tick; the #113 A/B re-measured affected shapes at parity or better (finite staggered 0.38 -> 0.19 ns/row, static staggered 0.19 -> 0.15-0.17, alternating ids 1.67 -> 1.22).

Binding and sets

  • MeasuredLanes.Measure(asset) folds each position once per direction and shares the tables across every set bind (issue #108): repeated binds run 21.6x faster; TimelineSet.Add(asset, measured) reuses one measurement.
  • New TickPurity enum and the PairRuntime<TTrack,TClip>.Consume(execute, bind, TickPurity.WindowConstant) overload: declared window-constant consumers let the measurer fill each constant clip window from one evaluation — 3.81 -> 0.92 ms on the #108 corpus — bit-identically to per-tick evaluation by construction. The default stays exact per-tick fill.

Authoring and tooling

  • Fused authoring JSON parse+populate (issue #108, PR #110): parse+bake 5.9x with diagnostics identical to the retained legacy oracle.
  • tlbake --json introspects the authoring schema (schemaVersion 1, golden-filed), --watch re-bakes on save, and test configs are JSON-driven (issue #115).
  • Blender bridge addon tools/Tl.Blender exports NLA scenes to authoring JSON and auto-fills type pairs from tlbake --json (issue #111).

Repository

  • The interactive cookbook site is live on Pages with embeddable recipes, linked from the README (issue #116).
  • The README documents the ECS-hosted execution shape and integration cost ladder (issue #102).
  • The scheduled benchmark workflow ran the removed Alpha --filter '*' --artifacts collector and failed on every push since the harness reduced to --verify; it now runs the supported verification invocation and no longer needs write permission.

Migrating from 1.0.0-alpha.6

Playback

alpha.6 alpha.7
var query = Timeline.Rows(rows).Write(health); query.Tick(gameTick, delta); BakedLane<TTrack,TClip>.Bind(asset); once per asset, then Timeline<BakedLane<TTrack,TClip>>.Seek(positions, forward).Apply(effects); once per frame
Timeline.TickUnmanaged(...) over UnmanagedTickState the same lane over caller-owned ushort position and float effect columns; UnmanagedTickState is gone
TimelineKernels.Register/Chain/Range, tlbake --kernel Kernels.g.cs removed without replacement; BakedLane.Bind measured tables are the only execution path; tlbake flags are now bake, --json, --watch, --strip, --cache, --report
multi-frame Tick(gameTick, delta) repeated single-frame Seek(...).Apply(...) calls (lag catch-up); no game clock exists anymore

The effect column is a plain caller-owned float[]/Span<float>; a lane consumer writes exactly one ref float and self-inverts through Frame.Direction, so rewind replays the negated fold.

Frame and state

alpha.6 alpha.7
Frame.GameTick (uint) removed; the engine has no game clock
Frame.Cycle (long) removed; count wraps from movement flags (recipe below)
Frame.TimelineTick (uint) ushort
— Frame.ClipLength, Frame.WithinClip (ushort window metadata)
TickFrame.GameTick, TickFrame.Cycle, TickFrame.TimelineTick (uint/long) TickFrame.TimelineTick (ushort) plus Flags; ToFrame(slot, tick, flags, ref scratch)
TimelineState.Cycle (long), TimelineState.Position (uint) TimelineState(ushort position) — 8 B; Select(..., ushort duration, ..., out ushort tick, out FrameFlags flags)
TimelineComponent.Position (uint) ushort
PairRuntime.Consume execute/range pointers (byte*, uint, uint, long, FrameFlags, void**, int) (byte*, ushort, FrameFlags, void**, int); the separate ConsumeUnmanaged overload is folded into Consume
Apply(Span<float>, Span<long>) cycle records Apply(Span<float>)

Durations above 65,535 ticks fail at bake and at TimelineAsset.Load with a diagnostic; split the asset or shorten the timeline.

Wrap counting from flags

The removed Cycle column counted completed loops per row. Reconstruct it on the host from the movement flags returned by TimelineMovement.Select, once per row per frame — exactly what the movement law exposes to the lane:

if (!TimelineMovement.Select(new TimelineState(asset, position), duration, looping, reverse, out var next, out _, out var flags))
    return;
if ((flags & FrameFlags.TimelineEnd) != 0)
    loops += (flags & FrameFlags.Reverse) != 0 ? -1 : 1;
position = next.Position;

Forward wraps set TimelineEnd with Reverse clear; backward wraps set both; finite completions carry CompletedAfter/CompletedBefore. The recipe is receipted against a randomized 48-row, 400-step schedule in tests/Tl.Alpha (WrapCounts), where flag-reconstructed counts equal the removed engine cycle.

Optional: window-constant consumers

Registering a consumer with PairRuntime<TTrack,TClip>.Consume(execute, bind, TickPurity.WindowConstant) declares its per-tick output depends only on the slot's track and clip data — never on Frame.TimelineTick, Frame.WithinClip, or flags. MeasuredLanes.Measure then fills each constant clip window from one evaluation, bit-identically to per-tick evaluation by construction. A declaration that lies is not detected: the table deterministically carries the window's first-tick evaluation, so only declare consumers you control. The default remains exact per-tick fill.

Artifacts

release-artifacts builds four packages (Tl.Runtime, Tl.Gen.CSharp, Tl.CSharp, Tl.Bake) plus the Tl.Runtime symbol package, a package-only NativeAOT smoke archive, the generation report, RELEASE-MANIFEST.json, and SHA256SUMS. The GitHub Release carries the artifact run's SHA-256 table; the publish job runs only through the protected nuget-production environment on the owner's explicit dispatch.

1.0.0-alpha.8

1.0.0-alpha.8 Pre-release
Pre-release

Choose a tag to compare

@IAFahim IAFahim released this 17 Sep 16:15

Prerelease over the data-authored surface: the bake command renames to tlb, the baked format pools unique values behind ushort indices, and the lane gains fused Advance entry points.

Command rename

  • tlbake is now tlb (#146, PR #147). Same flag surface: bake, --json, --watch, --strip, --cache, --report. Releases up to 1.0.0-alpha.7 installed the tool under tlbake; update with dotnet tool update --global Tl.Bake --prerelease.

TLB1 v2 value pools (#155, PR #158)

  • Unique track and clip values are pooled per pair behind fixed-width ushort slot indices; occurrences shrink to a fixed 32-byte shape and pair tables carry relative pool offsets. Assets baked by earlier alphas are rejected with a diagnostic — re-bake with the new tool. tlb --report gains pool/*/unique-count fields.
  • Bit-exact parity against the v1 inline-slot layout is receipted over a corpus covering every authoring JSON in the repository (samples, fixtures, presets, tests); warm-path speed is unchanged by construction.

Fused Advance entry points (#148, PR #153 — verdict: keep)

  • The typed playback lane and set gain fused single-call Advance entry points alongside Seek(...).Apply(...); receipts on #148.

Experiment verdicts

  • #149 (pooling authored values + trackIndex byte width): dead end, recorded with A/B and survey receipts (PR #154); superseded by the owner-directed #155 shape that shipped above.
  • #150 (Load-handle API): explicitly deferred to the 1.0.0 cycle — prototype receipts and the epoch-reclamation mandate are recorded on the issue.

Artifacts — built by the immutable release-artifacts run at this tag; SHA-256 table below. Packages publish to nuget.org through the protected publish-nuget workflow.

fd638ffad2f647e54eebfb5a51a4c826f11d0a395975a9931a8cfb66f6bcaacc  RELEASE-MANIFEST.json
b65c9f2416663b10fa7ce12894fb05170a3d3f1ea4a258c8fc1298a52d153cb3  Tl.Bake.1.0.0-alpha.8.nupkg
78d4a34e5ee1307602c5f7298710fd625985c616b155bf87709aab6f1c33c62c  Tl.CSharp.1.0.0-alpha.8.nupkg
7c49f3a03294658013894f36edbcb1433eb9f445b4623f0917790ad178c1fe29  Tl.Gen.CSharp.1.0.0-alpha.8.nupkg
4e406b84e1d4e8395baf54c96d9dfedf425a2ddd50ba5fb2ddaeea6f71ea4440  Tl.PackageConsumer-linux-x64.tar.gz
e3ae5d4640568215ccf03f846a60d05a65f1a6c3c7609d1d203b7d0beb0ad75b  Tl.PackageConsumer.generation-report.txt
d82828664928f0da75904e4c08c55b574f28cbb3b4b045dc775ef0b6db6e8f69  Tl.Runtime.1.0.0-alpha.8.nupkg
d6085f858f9bb729d4b07c905a9d298509addaff837918742e611ba121e0d91e  Tl.Runtime.1.0.0-alpha.8.snupkg

1.0.0-alpha.6

Choose a tag to compare

@IAFahim IAFahim released this 14 Sep 21:15

1.0.0-alpha.6

Performance and correctness release over the data-authored surface, driven by issues #87/#94 and audited end to end.

Kernel lane

  • Kernels generated by tlbake --kernel now bind metadata-bearing TLBs: the runtime hashes the same normalized hot view the emitter registers (issue #87). Stripped and full assets bind identically.
  • Fused single-pass mixed dispatch: TickMixed commits movement inline during the dispatch sweep and calls consumer range pointers directly (TimelineKernels.Range), removing the second sweep and the per-row chain walk.
  • 1M-row staggered facade lane 9.0 -> 5.6 ns/row; the remaining gap to the architecture ceiling is row-state traffic, addressed next by the per-asset playback-tables direction (#88/#56).

Binding

  • Shared native bind cache: repeated query construction over the same rows and columns rebinds in O(1) — 1,000 rebuilds/frame at 112 KB assets: 42 ms -> 0.18 ms. Late consumer and kernel registration are adopted through phase counters.
  • Kernel catalog moved to a native block with gated registration.

Generator and tooling

  • The source generator enforces the 4-slot consumer ABI: a fifth gameplay parameter is diagnostic TLGEN68 instead of silent neighbor-binding corruption.
  • --kernel output is LF-deterministic across platforms, padding-clean, and fully validated against malformed TLB1 input.

Repository

  • Four full-repo audits (docs, tests/CI, packaging, source) remediated: 60+ findings fixed including stale publication/licensing claims, a broken benchmark workflow, and missing CI coverage for the playback prototype.

SHA-256 (release-artifacts run 34895103252):

a173660566e2da28263bf621934223c535d4bb81c647b0c046fea8f284689f09  Tl.Bake.1.0.0-alpha.6.nupkg
2e8d6c21146e07038a717693d5f5d7c8a30ca64ef87101906a46cafcbf2ebf47  Tl.CSharp.1.0.0-alpha.6.nupkg
e8ad8085a504cc239fc337a2a91f141318f50b18e07e93bbfd0e18e4e1ad9956  Tl.Gen.CSharp.1.0.0-alpha.6.nupkg
136d5c47a113ca98ec6f84c447dac5ed344df38c413a24b176cadc410976afc8  Tl.Runtime.1.0.0-alpha.6.nupkg
638f601d91da3c588aaca1949fef62d6fb94c88f97345b02fc33e2da9c7670d1  Tl.Runtime.1.0.0-alpha.6.snupkg

v1.0.0-alpha.3

v1.0.0-alpha.3 Pre-release
Pre-release

Choose a tag to compare

@IAFahim IAFahim released this 11 Sep 04:57
628ebc6

v1.0.0-alpha.3

Alpha.3 replaces per-timeline playback facades and runtime IDs with generated catalog queries over borrowed component columns. One timeline can contain heterogeneous track and clip pairs. Each authored track binds one typed operation, and every row retains independent asset, position, and loop cycle while sharing the caller's game clock.

Public model

public readonly struct DamageJob : ITimelineJob<DamageTrack, DamageClip>
{
    public static void Execute(
        in Frame<DamageTrack, DamageClip> frame,
        in Resistance resistance,
        ref Health health)
        => health = new(health.Value - frame.Direction * frame.Clip.Amount
            * frame.Track.Multiplier * resistance.Scale);
}

public readonly partial struct Attack : ITimeline
{
    public static void Define(scoped Builder builder)
    {
        var damage = builder.Track(new DamageTrack(2f)).Use<DamageJob>();
        builder.Clip(damage, new DamageClip(10f), 5u, 6u);
    }
}

public readonly struct CombatRows;

public readonly partial struct Combat : ITimelineCatalog
{
    public static void Define(scoped CatalogBuilder builder)
        => builder.Schema<CombatRows>().Asset<Attack>();
}

var states = new[] { new Combat.State(Combat.Asset.Attack) };
var query = new Combat.Query().CombatRows(states, resistance, health);
query.Tick(200_000u, 3);
query.Tick(200_003u, -3);

Normal Roslyn and supporting IDE builds generate timelines and catalogs automatically. The execution API has no Build, Compile, InMemory, Bind, registration ID, Start, Stop, TrySeek, or per-tick failure value.

Semantics

  • A requested step selects each row without effects, executes typed stages in authored order, then commits each selected row once.
  • Reverse movement executes the exact reversed occurrence order.
  • Tick(G,+N) emits G..G+N-1; Tick(G,-N) emits G-1..G-N.
  • Finite assets clamp independently. Empty routes, gaps, empty definitions, completion, and zero delta are no-ops.
  • Nonempty loops use unsigned local position and signed cycle with explicit wrapping arithmetic.
  • One active clip is borrowed; two active clips resolve through Blend into one frame.
  • Jobs borrow unmanaged in and ref columns. Alpha.3 rejects out.
  • Schema construction rejects mismatched lengths, invalid routes, and prohibited writable aliases before effects.
  • A user exception preserves its executed effect prefix and prevents commit for that selected step.

See the complete API and execution semantics.

Packages

The GitHub prerelease contains five runtime/build packages, three symbol packages, a deterministic Unity UPM archive, a NativeAOT smoke archive, a generation report, a release manifest, and SHA-256 checksums. NuGet publication is disabled; install the .nupkg files from a local source.

Tl.CSharp is the recommended C# install. It depends on Tl.Runtime at application runtime and embeds Tl.Gen.CSharp.dll plus the compatible Tl.Compiler.dll as build-only analyzer/tool assets. Package-only JIT and NativeAOT receipts prove those toolchain assemblies do not enter application output.

The UPM package contains the Unity runtime boundary only. A package-only .NET authoring project materializes deterministic Unity-compatible C# before Unity compilation; compiler and Roslyn assets stay outside the player graph.

Tl.Gen.C retains its qualified C ABI v2 backend. It does not yet emit alpha.3 heterogeneous catalogs.

Verification

Exact production coverage at commit 9b1807686b9035b19a63133cd4f416ab1721e29b uses Microsoft dotnet-coverage 18.11.0 and eng/coverage.settings.xml:

Assembly Lines Branches
Tl.Core 100% 100%
Tl.Compiler 100% 100%
Tl.Gen.C 100% 100%
Tl.Gen.CSharp 100% 100%
Total 2,740 / 2,740 1,628 / 1,628

All 221 tests passed. The Cobertura report SHA-256 is ddf400d7341d8f92359d2d6ba250aadc22550f7aab069c628d5131f40243d544; the settings SHA-256 is ac6f6eaa04abe0f4929219523d0f2c0c204cef68e0ffc4f0ce87696fdaa63aa2. Coverage establishes executed source paths, not correctness by itself; oracle, boundary, allocation, package, AOT, and Unity receipts remain distinct gates.

The retained three-process full-query medians are 1.804 ns for one track, 3.754 ns for A-B-A, 21.230 ns for 16 tracks, 34,250.868 ns for 256 tracks, 1.997 ns for a gap, 2.555 ns for a blend, 2.256 ns for three inputs, and 34.650 ns/entity-tick for 256 mixed rows. Every arm reports 0 B. The <3 ns result applies only to the one-track, gap, blend, and three-input fixtures. The 256-track staged fallback remains open in issue #10.

Unity qualification covers stable Unity 6000.0.83f1, Entities 1.4.3, Burst 1.8.30 and the preview Unity 6000.7.0a5 lane. Both pass 4/4 EditMode and 4/4 PlayMode tests. Stable Mono and IL2CPP players execute Burst jobs, pass native leak scans, and exclude compiler/generator/Roslyn assemblies. The stable 10,000-row fixture measures 18.006 ns/entity-step, 20 jobs/step, and 0 main-thread managed B.

Production source plus paths is 231,796/250,000 B at coverage commit 9b1807686b9035b19a63133cd4f416ab1721e29b. Deterministic release artifacts are built twice, compared byte for byte, verified against fixed contents and identities, and recorded in one manifest and checksum set. The exact tagged artifact receipt belongs to release issue #35.

License selection and NuGet publication remain separate owner decisions and are not performed by the GitHub prerelease workflow.

See the migration guide, Unity guide, performance report, and release procedure.

tl v0.6.0

Choose a tag to compare

@IAFahim IAFahim released this 09 Sep 17:33
f3f91fe

v0.6 turns the compiled timeline path into a direct generated kernel. Runtime and compiled timelines now share one static per-work ITrack<TTrack,TClip,TInput,TResult> contract, and named ITimeline<TTrack,TClip> partial structs are both the authoring definition and the generated API.

Measured on .NET 10.0.11 and an i9-14900K:

Operation Runtime interpreter Compiled kernel Speedup
Full consumer, scalar 15.340 ns 4.489 ns 3.42x
Full consumer, batch 8 11.231 ns 4.104 ns 2.74x
Sum consumer, scalar 11.157 ns 2.473 ns 4.51x

Every measured arm allocated zero managed bytes. A narrower predictable batch experiment reached 1.195 ns/tick, establishing that the 1–2 ns throughput tier is feasible when the operation algebra is restricted.

This release also moves runtime timeline retention into one explicitly owned native block, removes generated managed arrays, adds scalar and batch specialization, bounds large generated source with compact per-track interval programs, and makes generator output deterministic and cacheable across unchanged builds and preprocessor configurations.

This is a pre-1.0 breaking release. Tracks<TTrack,TClip>, TrackWork<TTrack,TClip>, scratch overloads, and separate compiled consumer interfaces are removed. Existing consumers move their loop body into static ITrack.Forward and ITrack.Backward operations. The legacy Build(...).Compile() declaration remains for one migration release.

Release gates passed: 43 runtime tests, 60 generator tests, JIT and NativeAOT parity, runtime NativeAOT, all executable verification suites, package isolation, deterministic cache reuse, and the 178,299/200,000-byte source budget.

tl v0.5

Choose a tag to compare

@IAFahim IAFahim released this 08 Sep 22:03

tl v0.5

tl v0.5 packages the reviewed runtime and generator work as Tl.Runtime 0.5.0 and Tl.Gen 0.5.0. This release does not redesign the current repository's input/result callback API. NuGet users upgrading from the previously published 0.1.0 packages must follow the migration steps below.

Production changes

  • Runtime WorkSlot layout is 24 bytes, down from 28 bytes by removing padding. This saves 14.3% per slot without changing its values, constructor, or public API.
  • Generated Forward and Backward playback use direction-specialized closed methods. In the matched Pulse comparison, five of six measured shapes improved; repeated single-tick throughput regressed by about 1.8%. A consumer using both directions materialized 45.9% more native code for the measured Advance bodies, so this remains an explicit time/code-size tradeoff.
  • The compile cache now keys exact source membership and contents together with generator/dependency identity. Exact hits preserve output timestamps; missing or modified outputs are repaired; removed declarations remove only unchanged files previously owned by that invocation; unowned and user-modified obsolete files are preserved.
  • Declaration discovery is stricter. It ignores unrelated Compile calls, accepts supported qualified Tl receivers and unsigned literals, rejects output-name collisions, reports unsupported option expressions, and no longer resolves an explicit author qualifier to an unrelated compatible method. Ambiguous and unsupported forms fail closed.
  • Tl.Gen packages the Roslyn assemblies its CLI needs, and build-tool references no longer inherit application NativeAOT publish properties.
  • Benchmark history identities now include job, parameters, runtime environment, and ISA. Duplicate or missing measurements fail collection, while near-zero estimates are archived below the comparison floor.
  • CI enforces the 200,000-byte src content-plus-path budget and verifies benchmark collection, generated-output identity, JIT parity, NativeAOT parity, the full solution, packages, and existing receipts.

Runtime authoring uses Timeline<TTrack,TClip>.Build(author).InMemory(). Each handle owns one native block and must be released exactly once with Timeline.Destroy(id) after playback has stopped. Build, registration, binding, and destruction require the documented synchronization. Playback reads immutable snapshots concurrently; destroying a handle during in-flight playback is invalid.

Migrating from NuGet 0.1.0

The only previously published versions of both packages are 0.1.0, so package consumers receive the v0.2–v0.5 changes together:

  • Split the former mutable ref TData consumer parameter into immutable in TInput and mutable ref TResult, following the v0.2 migration.
  • End runtime authoring with Build(...).InMemory(), retain the returned handle, and call Timeline.Destroy(id) once after all playback has stopped. Follow the native ownership and threading contract.
  • For build-time C# specialization, use the supported Build(...).Compile() declaration grammar and call the emitted static kernel described in the compiled-path guide.

Validation

The reviewed release gates include 46 core tests, 52 generator tests, 7 collector tests, interpreter/generated sample parity, published NativeAOT execution, package restoration and execution from a clean local feed/cache, repeat installed-package generation with a cache hit, and a source footprint of 186,154/200,000 bytes including paths.

Experimental consumer fusion

The release repository also records a separate research backend for the exact four-track, nineteen-clip Pulse timeline and one explicit ordered floating-point sum/subtract operation. The final repeat measured the tree/carry batch at 1.334 ns/tick sequential, 1.246 ns/tick repeated, and 7.291 ns/tick random; its sequential scalar entry point measured 2.524 ns/tick. All measured arms reported zero managed allocation after setup.

This experiment is not the general .Compile() backend. It does not analyze arbitrary consumer C#, change the public API, promise universal 2 ns performance, or establish Unity or Burst compatibility. Production generated playback still uses the general view/callback path and retained arrays.

Full details, exact benchmark tables, migration guidance, evidence, and remaining work are in the v0.5 document.