Repository navigation
Releases
tl 4.0.0-preview.1
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
What's new
Wider consumer surface (#356/#357) — the settled 10/30 contract:
OnMemofolds up to 10 results per consumer with element widths 1/2/4/8 bytes (8-byte results fold into adjacent 4-byte lane pairs).OnActivetakes up to 30 live gameplay parameters (memo feeds included), bound byTypeKeythrough the caller-assembledColumnSet(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), genericApply<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 passasset.Index, scalar-dispatch callers pass a 1-element span. - Consumer bounds (#356):
OnMemoresult cap 4 → 10 (widths 1/2/4/8),OnActiveparameter cap 4 → 30, newColumnSetfor 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
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.inis rejected (TLGEN76: it runs at fold — no caller);stringand 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-carryingApply(ids, positions, forward, effects, input). Per-row live inputs change every frame.outis 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
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 throughApply(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
IBakemarker; 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
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)overBakedLane<TTrack,TClip>.Bind(asset)measured tables is the single warm path (issue #104, PR #106). TheTimeline.Rows(...)facade,TimelineKernelscatalog,TickUnmanagedwalk, andtlbake --kernelare 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
ushortunder a single 65,535-tick cap;TLB1load andtlbakereject larger durations with a diagnostic naming both numbers. CycleandGameTickare removed from the engine,Frame,TickFrame,TimelineState,TimelineComponent, and the consumerExecute/Rangeseam;Frame.ClipLengthandFrame.WithinClipexpose the slot window instead.- Movement records halve to 8 B,
TimelineState16 -> 8 B,TimelineComponent24 -> 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
TickPurityenum and thePairRuntime<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 --jsonintrospects the authoring schema (schemaVersion1, golden-filed),--watchre-bakes on save, and test configs are JSON-driven (issue #115).- Blender bridge addon
tools/Tl.Blenderexports NLA scenes to authoring JSON and auto-fills type pairs fromtlbake --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
benchmarkworkflow ran the removedAlpha --filter '*' --artifactscollector 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
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
tlbakeis nowtlb(#146, PR #147). Same flag surface: bake,--json,--watch,--strip,--cache,--report. Releases up to 1.0.0-alpha.7 installed the tool undertlbake; update withdotnet 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 --reportgainspool/*/unique-countfields. - 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
Advanceentry points alongsideSeek(...).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
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 --kernelnow 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:
TickMixedcommits 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.
--kerneloutput 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
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)emitsG..G+N-1;Tick(G,-N)emitsG-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
Blendinto one frame. - Jobs borrow unmanaged
inandrefcolumns. Alpha.3 rejectsout. - 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
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
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
WorkSlotlayout 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
Compilecalls, 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.Genpackages 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
srccontent-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 TDataconsumer parameter into immutablein TInputand mutableref TResult, following the v0.2 migration. - End runtime authoring with
Build(...).InMemory(), retain the returned handle, and callTimeline.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.