Two builds of one capture share a creation time, and the registry broke the tie by stamp -- an arbitrary winner, so a rebuild under a changed rule could lose to the build it was meant to replace, and superseded builds had to be pruned by hand. The version now carries a policy revision: bumped when generate's rules change in a way that should reach every library, decisive between builds of one capture, and still outweighed by any newer capture. Bundles from before the field read as revision zero and lose to any revised build, which retires the hand-pruning.
- commit
- 58fe99834d99b705e93c911f4064477aab1250d5
- parent
- e9ecf815892a1580fe272aebb0ebdc7360ef8a69
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 5 | +++++ |
| M | internal/ | 26 | ++++++++++++++++++++++++++ |
| M | internal/ | 3 | ++- |
| M | internal/ | 8 | ++++++-- |
| M | tools/ | 13 | +++++++++++++ |
@@ -50,9 +50,14 @@ type Game struct {
5050 // Version distinguishes builds of the same game. The stamp is a content5151 // fingerprint -- two bundles with equal stamps hold the same data -- and5252 // CreatedAt orders them, so a newer bundle dropped beside an older one wins.53+// Revision orders builds of the same capture: the data has not moved, but54+// the policy that turns a capture into a bundle has, and the build under the55+// newer policy is the one that should serve. Bundles from before the field56+// existed read as revision zero and lose to any revised build.5357 type Version struct {5458 Stamp string `json:"stamp"`5559 CreatedAt string `json:"createdAt"`60+ Revision int `json:"revision,omitempty"`5661 }5762 5863 // TileGrid is the window this game's maps are cut from: the zoom the source@@ -247,6 +247,32 @@ func TestMoreRecentIsTotalAndPrefersNewer(t *testing.T) {
247247 }248248 }249249 250+// Builds of one capture share a creation time; the policy revision decides251+// between them, so a rebuild under a newer rule supersedes deterministically252+// rather than by whichever stamp happens to sort higher.253+func TestMoreRecentPrefersTheNewerRevisionOfOneCapture(t *testing.T) {254+ captured := "2026-06-01T00:00:00Z"255+ plain := open(t, bundletest.Build(t, t.TempDir(), bundletest.Spec{256+ Slug: "game", CreatedAt: captured,257+ Stamp: strings.Repeat("ff", 32),258+ }))259+ revised := open(t, bundletest.Build(t, t.TempDir(), bundletest.Spec{260+ Slug: "game", CreatedAt: captured, Revision: 1,261+ Stamp: strings.Repeat("00", 32),262+ }))263+264+ if !bundle.MoreRecent(revised, plain) || bundle.MoreRecent(plain, revised) {265+ t.Error("the revision does not decide between builds of one capture")266+ }267+268+ newerCapture := open(t, bundletest.Build(t, t.TempDir(), bundletest.Spec{269+ Slug: "game", CreatedAt: "2026-07-01T00:00:00Z",270+ }))271+ if !bundle.MoreRecent(newerCapture, revised) {272+ t.Error("a revision outweighed a newer capture")273+ }274+}275+250276 func open(t *testing.T, path string) *bundle.Bundle {251277 t.Helper()252278 opened, err := bundle.Open(path)@@ -22,6 +22,7 @@ type Spec struct {
2222 Title string2323 CreatedAt string2424 Stamp string25+ Revision int2526 Maps []MapSpec2627 }2728 @@ -60,7 +61,7 @@ func Build(t testing.TB, dir string, spec Spec) string {
6061 Format: bundle.Format,6162 FormatVersion: bundle.FormatVersion,6263 Game: bundle.Game{Slug: spec.Slug, Title: spec.Title},63- Version: bundle.Version{Stamp: spec.Stamp, CreatedAt: spec.CreatedAt},64+ Version: bundle.Version{Stamp: spec.Stamp, CreatedAt: spec.CreatedAt, Revision: spec.Revision},6465 TileGrid: bundle.TileGrid{SourceZoom: 13, FirstTile: 4064, TileSize: 256, Size: 8192},6566 }6667 for _, m := range spec.Maps {@@ -37,12 +37,16 @@ func HashBytes(data []byte) string {
3737 }3838 3939 // MoreRecent reports whether a should shadow b when both claim the same game.40-// Creation time decides; the stamp and then the path break ties, so the choice41-// is total and two scans of the same directory always agree.40+// Creation time decides; among builds of the same capture the newer policy41+// revision decides; the stamp and then the path break the remaining ties, so42+// the choice is total and two scans of the same directory always agree.4243 func MoreRecent(a, b *Bundle) bool {4344 if a.Manifest.Version.CreatedAt != b.Manifest.Version.CreatedAt {4445 return a.Manifest.Version.CreatedAt > b.Manifest.Version.CreatedAt4546 }47+ if a.Manifest.Version.Revision != b.Manifest.Version.Revision {48+ return a.Manifest.Version.Revision > b.Manifest.Version.Revision49+ }4650 if a.Manifest.Version.Stamp != b.Manifest.Version.Stamp {4751 return a.Manifest.Version.Stamp > b.Manifest.Version.Stamp4852 }@@ -13,6 +13,13 @@ import (
1313 "github.com/FelineStateMachine/atlas/internal/bundle"1414 )1515 16+// policyRevision orders builds of the same capture. The data has not moved17+// between them, but what this tool makes of a capture has -- a merge rule, a18+// kept field, a dropped category -- and among equal captures the registry19+// serves the highest revision. Bump it when a policy change should supersede20+// the builds already in every library.21+const policyRevision = 122+1623 // writeBundles packs each game into its own .atlas file, named by game,1724 // capture day, and stamp. The directory is a registry, not a mirror: a new1825 // capture lands beside the builds before it rather than over them, nothing@@ -54,6 +61,7 @@ func writeGameBundle(
5461 Format: bundle.Format,5562 FormatVersion: bundle.FormatVersion,5663 Game: bundle.Game{Slug: game.Slug, Title: game.Title},64+ Version: bundle.Version{Revision: policyRevision},5765 TileGrid: bundle.TileGrid{5866 SourceZoom: grid.SourceZoom,5967 FirstTile: grid.FirstTile,@@ -208,6 +216,7 @@ type registryVersion struct {
208216 File string `json:"file"`209217 Stamp string `json:"stamp"`210218 CreatedAt string `json:"createdAt"`219+ Revision int `json:"revision,omitempty"`211220 Size int64 `json:"size"`212221 Maps int `json:"maps"`213222 }@@ -256,6 +265,7 @@ func writeRegistryIndex(bundleDir string) error {
256265 File: filepath.Base(path),257266 Stamp: manifest.Version.Stamp,258267 CreatedAt: manifest.Version.CreatedAt,268+ Revision: manifest.Version.Revision,259269 Size: size,260270 Maps: len(manifest.Maps),261271 })@@ -267,6 +277,9 @@ func writeRegistryIndex(bundleDir string) error {
267277 if entry.Versions[i].CreatedAt != entry.Versions[j].CreatedAt {268278 return entry.Versions[i].CreatedAt > entry.Versions[j].CreatedAt269279 }280+ if entry.Versions[i].Revision != entry.Versions[j].Revision {281+ return entry.Versions[i].Revision > entry.Versions[j].Revision282+ }270283 return entry.Versions[i].Stamp > entry.Versions[j].Stamp271284 })272285 listed = append(listed, *entry)