lib/gui/src/paint/fallback.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const alloc_phase = @import("alloc_phase");
  3 const filigree = @import("filigree");
  4 
  5 const Allocator = std.mem.Allocator;
  6 
  7 pub const FaceSegment = struct {
  8     source: filigree.SourceRange,
  9     candidate_index: usize,
 10     missing_everywhere: bool,
 11 };
 12 
 13 pub const Key = struct {
 14     content: []const u8,
 15     primary_identity: usize,
 16     fallback_identity: usize,
 17 };
 18 
 19 const CacheEntry = struct {
 20     hash: u64,
 21     primary_identity: usize,
 22     fallback_identity: usize,
 23     content_offset: usize,
 24     content_len: usize,
 25     segment_offset: usize,
 26     segment_count: usize,
 27 };
 28 
 29 pub const Limits = struct {
 30     max_source_units: usize,
 31     cache_entries: usize,
 32     cache_payload_bytes: usize,
 33 };
 34 
 35 pub const DeriveError = error{CapacityOverflow};
 36 
 37 const storage_alignment: usize = @max(
 38     @max(@alignOf(filigree.FallbackSegment), @alignOf(u32)),
 39     @max(@alignOf(CacheEntry), @alignOf(FaceSegment)),
 40 );
 41 
 42 pub const Capacity = struct {
 43     limits: Limits,
 44     segment_offset: usize,
 45     segment_bytes: usize,
 46     index_slots: usize,
 47     index_offset: usize,
 48     index_bytes: usize,
 49     entry_offset: usize,
 50     entry_bytes: usize,
 51     payload_offset: usize,
 52     payload_bytes: usize,
 53     storage_bytes: usize,
 54 
 55     pub fn derive(limits: Limits) DeriveError!Capacity {
 56         const segments = try placed(filigree.FallbackSegment, 0, limits.max_source_units);
 57         const slots = try indexSlotCount(limits.cache_entries);
 58         const index = try placed(u32, segments.end, slots);
 59         const entries = try placed(CacheEntry, index.end, limits.cache_entries);
 60         const payload = try placed(u8, try alignOffset(entries.end, @alignOf(FaceSegment)), limits.cache_payload_bytes);
 61         return .{
 62             .limits = limits,
 63             .segment_offset = segments.start,
 64             .segment_bytes = segments.bytes,
 65             .index_slots = slots,
 66             .index_offset = index.start,
 67             .index_bytes = index.bytes,
 68             .entry_offset = entries.start,
 69             .entry_bytes = entries.bytes,
 70             .payload_offset = payload.start,
 71             .payload_bytes = payload.bytes,
 72             .storage_bytes = payload.end,
 73         };
 74     }
 75 };
 76 
 77 pub const Exhaustion = error{
 78     SourceUnitCapacityExceeded,
 79     SegmentCapacityExceeded,
 80     CacheEntryCapacityExceeded,
 81     CachePayloadCapacityExceeded,
 82 };
 83 
 84 pub const Status = struct {
 85     phase: alloc_phase.capacity.Phase,
 86     capacity: StorageCapacity,
 87     storage_bytes: usize,
 88     entries: usize,
 89     payload_bytes: usize,
 90     high_water_entries: usize,
 91     high_water_payload_bytes: usize,
 92     source_rejections: u64,
 93     segment_rejections: u64,
 94     cache_entry_rejections: u64,
 95     cache_payload_rejections: u64,
 96     rollovers: u64,
 97 };
 98 
 99 const StorageLimits = Limits;
100 const StorageCapacity = Capacity;
101 const StorageExhaustion = Exhaustion;
102 
103 pub const Storage = struct {
104     phase: alloc_phase.capacity.Phase,
105     capacity: StorageCapacity,
106     bytes: []align(storage_alignment) u8,
107     segment_scratch: []filigree.FallbackSegment,
108     index: []u32,
109     entries: []CacheEntry,
110     payload: []u8,
111     entry_count: usize = 0,
112     payload_used: usize = 0,
113     high_water_entries: usize = 0,
114     high_water_payload_bytes: usize = 0,
115     source_rejections: u64 = 0,
116     segment_rejections: u64 = 0,
117     cache_entry_rejections: u64 = 0,
118     cache_payload_rejections: u64 = 0,
119     rollovers: u64 = 0,
120 
121     pub const Limits: type = StorageLimits;
122     pub const Capacity: type = StorageCapacity;
123     pub const Exhaustion: type = StorageExhaustion;
124     pub const InitError = Allocator.Error || DeriveError;
125 
126     pub const claim: alloc_phase.capacity.Declaration = .{
127         .source = .{
128             .id = "gui.text_fallback_segment_storage",
129             .kind = .phase_static,
130             .limit_source = .caller,
131             .storage = .{
132                 .covered = &.{
133                     .{
134                         .id = "fallback_reported_segment_scratch",
135                         .lifetime = .steady,
136                         .detail = "reported Filigree fallback face segments",
137                     },
138                     .{
139                         .id = "fallback_segment_cache_index_and_entries",
140                         .lifetime = .steady,
141                         .detail = "fixed fallback segment cache index and dense entries",
142                     },
143                     .{
144                         .id = "fallback_segment_cache_payload",
145                         .lifetime = .steady,
146                         .detail = "source keys and aligned face segment payloads",
147                     },
148                 },
149                 .excluded = &.{
150                     "caller-owned UTF-8 content and font atlases",
151                     "Filigree fallback context and temporary shaped output",
152                     "per-atlas shape caches and composite layout workspace",
153                 },
154             },
155             .capacity = .{
156                 .inputs = &.{
157                     alloc_phase.capacity.bindInput(StorageLimits, "max_source_units", "max_source_units"),
158                     alloc_phase.capacity.bindInput(StorageLimits, "cache_entries", "cache_entries"),
159                     alloc_phase.capacity.bindInput(StorageLimits, "cache_payload_bytes", "cache_payload_bytes"),
160                 },
161                 .type_selectors = &.{
162                     alloc_phase.capacity.bindType(filigree.FallbackSegment, "fallbacksegment"),
163                     alloc_phase.capacity.bindType(u32, "u32"),
164                     alloc_phase.capacity.bindType(CacheEntry, "cacheentry"),
165                     alloc_phase.capacity.bindType(FaceSegment, "facesegment"),
166                 },
167                 .nodes = &.{
168                     .{ .input = 0 },
169                     .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 } } },
170                     .{ .alignment = .{ .node = 1, .alignment = .{ .concrete_type = 1 } } },
171                     .{ .input = 1 },
172                     .{ .scale = .{ .node = 3, .coefficient = .{ .literal = 2 } } },
173                     .{ .next_power_of_two = 4 },
174                     .{ .scale = .{ .node = 5, .coefficient = .{ .size_of_concrete_type = 1 } } },
175                     .{ .add = .{ .left = 2, .right = 6 } },
176                     .{ .alignment = .{ .node = 7, .alignment = .{ .concrete_type = 2 } } },
177                     .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 2 } } },
178                     .{ .add = .{ .left = 8, .right = 9 } },
179                     .{ .alignment = .{ .node = 10, .alignment = .{ .concrete_type = 3 } } },
180                     .{ .input = 2 },
181                     .{ .add = .{ .left = 11, .right = 12 } },
182                 },
183                 .assertions = &.{.{
184                     .scope = .closure_total,
185                     .measure = .retained,
186                     .relation = .exact,
187                     .expression = 13,
188                 }},
189             },
190             .overload = .{
191                 .kind = .reject_before_mutation,
192                 .detail = "source, segment, entry, and payload max-plus-one requests reject before cached records or returned pointers change",
193             },
194             .risks = .{
195                 .transitive = .{
196                     .status = .witnessed,
197                     .detail = "activated lookup, insertion, and explicit rollover use only the acquired typed regions",
198                 },
199                 .foreign = .{
200                     .status = .excluded,
201                     .detail = "cache operations cross no operating-system or foreign callback boundary",
202                 },
203             },
204             .obligations = &.{
205                 .{ .key = "gui_text_fallback_capacity", .role = .capacity_model },
206                 .{ .key = "gui_text_fallback_acquisition", .role = .custom },
207                 .{ .key = "gui_text_fallback_oom", .role = .custom },
208                 .{ .key = "gui_text_fallback_boundaries", .role = .overload },
209                 .{ .key = "gui_text_fallback_reuse", .role = .overload },
210                 .{ .key = "gui_text_fallback_sealed_transitive_risk", .role = .transitive_risk },
211                 .{ .key = "gui_text_fallback_sealed_foreign_risk", .role = .foreign_risk },
212                 .{ .key = "gui_text_fallback_root", .role = .custom },
213             },
214         },
215         .bindings = .{
216             .owner = @This(),
217             .seal = .{
218                 .family = alloc_phase.capacity.selector(@This().activate),
219                 .premise = .{
220                     .class = .checked_semantic_fact,
221                     .authority = .checker,
222                 },
223             },
224             .teardown = .{
225                 .family = alloc_phase.capacity.selector(@This().deinit),
226                 .premise = .{
227                     .class = .checked_semantic_fact,
228                     .authority = .checker,
229                 },
230             },
231         },
232     };
233 
234     pub fn init(allocator: Allocator, limits: StorageLimits) InitError!Storage {
235         const capacity = try StorageCapacity.derive(limits);
236         const bytes = try allocator.alignedAlloc(u8, .fromByteUnits(storage_alignment), capacity.storage_bytes);
237         return .{
238             .phase = .initialization,
239             .capacity = capacity,
240             .bytes = bytes,
241             .segment_scratch = typedSlice(filigree.FallbackSegment, bytes, capacity.segment_offset, limits.max_source_units),
242             .index = typedSlice(u32, bytes, capacity.index_offset, capacity.index_slots),
243             .entries = typedSlice(CacheEntry, bytes, capacity.entry_offset, limits.cache_entries),
244             .payload = bytes[capacity.payload_offset..][0..capacity.payload_bytes],
245         };
246     }
247 
248     pub fn activate(self: *Storage) void {
249         std.debug.assert(self.phase == .initialization);
250         self.assertStorage();
251         @memset(self.index, 0);
252         self.phase = .steady;
253     }
254 
255     pub fn deinit(self: *Storage, allocator: Allocator) void {
256         std.debug.assert(self.phase != .teardown);
257         self.assertStorage();
258         self.phase = .teardown;
259         allocator.free(self.bytes);
260         self.bytes = &.{};
261         self.segment_scratch = &.{};
262         self.index = &.{};
263         self.entries = &.{};
264         self.payload = &.{};
265     }
266 
267     pub fn segmentList(self: *Storage, source_units: usize) StorageExhaustion!std.ArrayListUnmanaged(filigree.FallbackSegment) {
268         std.debug.assert(self.phase == .steady);
269         if (source_units > self.capacity.limits.max_source_units) {
270             self.source_rejections +|= 1;
271             return error.SourceUnitCapacityExceeded;
272         }
273         return .{ .items = self.segment_scratch[0..0], .capacity = self.segment_scratch.len };
274     }
275 
276     pub fn lookup(self: *const Storage, key: Key) ?[]const FaceSegment {
277         std.debug.assert(self.phase == .steady);
278         if (self.index.len == 0) return null;
279         const hash = hashKey(key);
280         var slot = @as(usize, @truncate(hash)) & (self.index.len - 1);
281         var probes: usize = 0;
282         while (probes < self.index.len) : (probes += 1) {
283             const stored = self.index[slot];
284             if (stored == 0) return null;
285             const entry = self.entries[stored - 1];
286             if (self.entryMatches(entry, hash, key)) return self.entrySegments(entry);
287             slot = (slot + 1) & (self.index.len - 1);
288         }
289         return null;
290     }
291 
292     pub fn store(self: *Storage, key: Key, reported: []const filigree.FallbackSegment) StorageExhaustion![]const FaceSegment {
293         std.debug.assert(self.phase == .steady);
294         if (reported.len > self.segment_scratch.len) {
295             self.segment_rejections +|= 1;
296             return error.SegmentCapacityExceeded;
297         }
298         if (self.entry_count >= self.entries.len) {
299             self.cache_entry_rejections +|= 1;
300             return error.CacheEntryCapacityExceeded;
301         }
302         const placement = payloadPlacement(self.payload_used, key.content.len, reported.len) catch {
303             self.cache_payload_rejections +|= 1;
304             return error.CachePayloadCapacityExceeded;
305         };
306         if (placement.end > self.payload.len) {
307             self.cache_payload_rejections +|= 1;
308             return error.CachePayloadCapacityExceeded;
309         }
310         const hash = hashKey(key);
311         const slot = self.emptySlot(hash) orelse {
312             self.cache_entry_rejections +|= 1;
313             return error.CacheEntryCapacityExceeded;
314         };
315         return self.commitStore(key, reported, hash, slot, placement);
316     }
317 
318     pub fn reset(self: *Storage) void {
319         std.debug.assert(self.phase == .steady);
320         @memset(self.index, 0);
321         self.entry_count = 0;
322         self.payload_used = 0;
323         self.rollovers +|= 1;
324     }
325 
326     pub fn status(self: *const Storage) Status {
327         return .{
328             .phase = self.phase,
329             .capacity = self.capacity,
330             .storage_bytes = self.capacity.storage_bytes,
331             .entries = self.entry_count,
332             .payload_bytes = self.payload_used,
333             .high_water_entries = self.high_water_entries,
334             .high_water_payload_bytes = self.high_water_payload_bytes,
335             .source_rejections = self.source_rejections,
336             .segment_rejections = self.segment_rejections,
337             .cache_entry_rejections = self.cache_entry_rejections,
338             .cache_payload_rejections = self.cache_payload_rejections,
339             .rollovers = self.rollovers,
340         };
341     }
342 
343     fn commitStore(self: *Storage, key: Key, reported: []const filigree.FallbackSegment, hash: u64, slot: usize, placement: PayloadPlacement) []const FaceSegment {
344         const content = self.payload[placement.content_offset..placement.content_end];
345         @memcpy(content, key.content);
346         const segments = payloadSegments(self.payload, placement.segment_offset, reported.len);
347         for (reported, segments) |source, *target| {
348             target.* = .{
349                 .source = source.source,
350                 .candidate_index = source.candidate_index,
351                 .missing_everywhere = source.missing_everywhere,
352             };
353         }
354         self.entries[self.entry_count] = .{
355             .hash = hash,
356             .primary_identity = key.primary_identity,
357             .fallback_identity = key.fallback_identity,
358             .content_offset = placement.content_offset,
359             .content_len = key.content.len,
360             .segment_offset = placement.segment_offset,
361             .segment_count = reported.len,
362         };
363         self.index[slot] = @intCast(self.entry_count + 1);
364         self.entry_count += 1;
365         self.payload_used = placement.end;
366         self.high_water_entries = @max(self.high_water_entries, self.entry_count);
367         self.high_water_payload_bytes = @max(self.high_water_payload_bytes, self.payload_used);
368         return segments;
369     }
370 
371     fn emptySlot(self: *const Storage, hash: u64) ?usize {
372         if (self.index.len == 0) return null;
373         var slot = @as(usize, @truncate(hash)) & (self.index.len - 1);
374         var probes: usize = 0;
375         while (probes < self.index.len) : (probes += 1) {
376             if (self.index[slot] == 0) return slot;
377             slot = (slot + 1) & (self.index.len - 1);
378         }
379         return null;
380     }
381 
382     fn entryMatches(self: *const Storage, entry: CacheEntry, hash: u64, key: Key) bool {
383         if (entry.hash != hash or entry.primary_identity != key.primary_identity or entry.fallback_identity != key.fallback_identity) return false;
384         if (entry.content_len != key.content.len) return false;
385         return std.mem.eql(u8, self.payload[entry.content_offset..][0..entry.content_len], key.content);
386     }
387 
388     fn entrySegments(self: *const Storage, entry: CacheEntry) []const FaceSegment {
389         return payloadSegments(self.payload, entry.segment_offset, entry.segment_count);
390     }
391 
392     fn assertStorage(self: *const Storage) void {
393         std.debug.assert(self.bytes.len == self.capacity.storage_bytes);
394         std.debug.assert(self.segment_scratch.len == self.capacity.limits.max_source_units);
395         std.debug.assert(self.index.len == self.capacity.index_slots);
396         std.debug.assert(self.entries.len == self.capacity.limits.cache_entries);
397         std.debug.assert(self.payload.len == self.capacity.payload_bytes);
398         std.debug.assert(self.entry_count <= self.entries.len);
399         std.debug.assert(self.payload_used <= self.payload.len);
400     }
401 };
402 
403 comptime {
404     alloc_phase.capacity.requireAllocatorRejectingOwnerShape(Storage);
405 }
406 
407 pub const Engine = struct {
408     allocator: Allocator,
409     context: filigree.FallbackContext,
410     output: filigree.Output,
411     storage: Storage,
412 
413     pub fn init(allocator: Allocator, limits: Limits, output_limits: filigree.Output.Limits) !Engine {
414         var storage = try Storage.init(allocator, limits);
415         errdefer storage.deinit(allocator);
416         storage.activate();
417         var context = try filigree.FallbackContext.init(allocator, .{
418             .script_runs = .{ .max_source_units = limits.max_source_units },
419             .output = output_limits,
420         });
421         errdefer context.deinit();
422         return .{
423             .allocator = allocator,
424             .context = context,
425             .output = try filigree.Output.init(allocator, output_limits),
426             .storage = storage,
427         };
428     }
429 
430     pub fn deinit(self: *Engine) void {
431         self.storage.deinit(self.allocator);
432         self.output.deinit(self.allocator);
433         self.context.deinit();
434         self.* = undefined;
435     }
436 
437     pub fn segments(self: *Engine, primary: *const filigree.Font, fallback: *const filigree.Font, content: []const u8) ![]const FaceSegment {
438         const key = Key{
439             .content = content,
440             .primary_identity = @intFromPtr(primary),
441             .fallback_identity = @intFromPtr(fallback),
442         };
443         if (self.storage.lookup(key)) |cached| return cached;
444         var reported = try self.storage.segmentList(content.len);
445         const candidates = [_]filigree.FallbackCandidate{.{ .font = fallback }};
446         try self.context.shapeRunSegmented(.{
447             .base = .{ .font = primary, .text = .{ .utf8 = content } },
448             .candidates = &candidates,
449         }, &self.output, self.allocator, &reported);
450         return try self.storage.store(key, reported.items);
451     }
452 
453     pub fn status(self: *const Engine) Status {
454         return self.storage.status();
455     }
456 };
457 
458 const Region = struct {
459     start: usize,
460     bytes: usize,
461     end: usize,
462 };
463 
464 const PayloadPlacement = struct {
465     content_offset: usize,
466     content_end: usize,
467     segment_offset: usize,
468     end: usize,
469 };
470 
471 fn placed(comptime T: type, offset: usize, count: usize) DeriveError!Region {
472     const start = try alignOffset(offset, @alignOf(T));
473     const bytes = std.math.mul(usize, count, @sizeOf(T)) catch return error.CapacityOverflow;
474     return .{ .start = start, .bytes = bytes, .end = std.math.add(usize, start, bytes) catch return error.CapacityOverflow };
475 }
476 
477 fn alignOffset(offset: usize, alignment: usize) DeriveError!usize {
478     const padded = std.math.add(usize, offset, alignment - 1) catch return error.CapacityOverflow;
479     return padded & ~(alignment - 1);
480 }
481 
482 fn indexSlotCount(entries: usize) DeriveError!usize {
483     if (entries == 0) return 0;
484     const doubled = std.math.mul(usize, entries, 2) catch return error.CapacityOverflow;
485     return std.math.ceilPowerOfTwo(usize, doubled) catch return error.CapacityOverflow;
486 }
487 
488 fn payloadPlacement(offset: usize, content_len: usize, segment_count: usize) DeriveError!PayloadPlacement {
489     const content_end = std.math.add(usize, offset, content_len) catch return error.CapacityOverflow;
490     const segment_offset = try alignOffset(content_end, @alignOf(FaceSegment));
491     const segment_bytes = std.math.mul(usize, segment_count, @sizeOf(FaceSegment)) catch return error.CapacityOverflow;
492     return .{
493         .content_offset = offset,
494         .content_end = content_end,
495         .segment_offset = segment_offset,
496         .end = std.math.add(usize, segment_offset, segment_bytes) catch return error.CapacityOverflow,
497     };
498 }
499 
500 fn typedSlice(comptime T: type, bytes: []align(storage_alignment) u8, offset: usize, count: usize) []T {
501     const byte_count = count * @sizeOf(T);
502     const region: []align(@alignOf(T)) u8 = @alignCast(bytes[offset..][0..byte_count]);
503     return std.mem.bytesAsSlice(T, region);
504 }
505 
506 fn payloadSegments(payload: []u8, offset: usize, count: usize) []FaceSegment {
507     const byte_count = count * @sizeOf(FaceSegment);
508     const region: []align(@alignOf(FaceSegment)) u8 = @alignCast(payload[offset..][0..byte_count]);
509     return std.mem.bytesAsSlice(FaceSegment, region);
510 }
511 
512 fn hashKey(key: Key) u64 {
513     var hash = std.hash.Wyhash.init(0x17d6_43c1_a948_5ef2);
514     hash.update(std.mem.asBytes(&key.primary_identity));
515     hash.update(std.mem.asBytes(&key.fallback_identity));
516     hash.update(key.content);
517     return hash.final();
518 }
519 
520 fn modelCapacity(limits: Limits) DeriveError!Capacity {
521     const segment_bytes = @as(u128, limits.max_source_units) * @sizeOf(filigree.FallbackSegment);
522     const index_slots = if (limits.cache_entries == 0) 0 else std.math.ceilPowerOfTwo(u128, @as(u128, limits.cache_entries) * 2) catch return error.CapacityOverflow;
523     const index_offset = modelAlign(segment_bytes, @alignOf(u32));
524     const index_bytes = index_slots * @sizeOf(u32);
525     const entry_offset = modelAlign(index_offset + index_bytes, @alignOf(CacheEntry));
526     const entry_bytes = @as(u128, limits.cache_entries) * @sizeOf(CacheEntry);
527     const payload_offset = modelAlign(entry_offset + entry_bytes, @alignOf(FaceSegment));
528     const storage_bytes = payload_offset + limits.cache_payload_bytes;
529     const values = [_]u128{ segment_bytes, index_slots, index_offset, index_bytes, entry_offset, entry_bytes, payload_offset, storage_bytes };
530     for (values) |value| if (value > std.math.maxInt(usize)) return error.CapacityOverflow;
531     return .{
532         .limits = limits,
533         .segment_offset = 0,
534         .segment_bytes = @intCast(segment_bytes),
535         .index_slots = @intCast(index_slots),
536         .index_offset = @intCast(index_offset),
537         .index_bytes = @intCast(index_bytes),
538         .entry_offset = @intCast(entry_offset),
539         .entry_bytes = @intCast(entry_bytes),
540         .payload_offset = @intCast(payload_offset),
541         .payload_bytes = limits.cache_payload_bytes,
542         .storage_bytes = @intCast(storage_bytes),
543     };
544 }
545 
546 fn modelAlign(offset: u128, alignment: u128) u128 {
547     return ((offset + alignment - 1) / alignment) * alignment;
548 }
549 
550 fn activatedStorage(allocator: Allocator, limits: Limits) !Storage {
551     var storage = try Storage.init(allocator, limits);
552     storage.activate();
553     return storage;
554 }
555 
556 fn checkInitFailures(allocator: Allocator) !void {
557     var storage = try Storage.init(allocator, .{ .max_source_units = 16, .cache_entries = 4, .cache_payload_bytes = 256 });
558     storage.deinit(allocator);
559 }
560 
561 test "fallback segment capacity matches an independent aligned byte model" {
562     comptime {
563         @stardustClaim(alloc_phase.capacity.witness(Storage, "gui_text_fallback_capacity"), null, null, null, null, null, null);
564     }
565     const limits = Limits{ .max_source_units = 13, .cache_entries = 7, .cache_payload_bytes = 513 };
566     try std.testing.expectEqual(try modelCapacity(limits), try Capacity.derive(limits));
567 }
568 
569 test "fallback segment storage acquires one exact region and retries OOM" {
570     comptime {
571         @stardustClaim(
572             alloc_phase.capacity.witness(Storage, "gui_text_fallback_acquisition"),
573             null,
574             null,
575             null,
576             null,
577             null,
578             null,
579         );
580     }
581     comptime {
582         @stardustClaim(
583             alloc_phase.capacity.witness(Storage, "gui_text_fallback_oom"),
584             null,
585             null,
586             null,
587             null,
588             null,
589             null,
590         );
591     }
592     const limits = Limits{ .max_source_units = 13, .cache_entries = 7, .cache_payload_bytes = 513 };
593     const capacity = try Capacity.derive(limits);
594     var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{});
595     var storage = try Storage.init(counting.allocator(), limits);
596     defer storage.deinit(counting.allocator());
597     try std.testing.expectEqual(@as(usize, 1), counting.alloc_index);
598     try std.testing.expectEqual(capacity.storage_bytes, counting.allocated_bytes);
599     storage.activate();
600     try std.testing.checkAllAllocationFailures(std.testing.allocator, checkInitFailures, .{});
601 }
602 
603 test "fallback segment storage accepts exact maxima and rejects max plus one transactionally" {
604     comptime {
605         @stardustClaim(
606             alloc_phase.capacity.witness(Storage, "gui_text_fallback_boundaries"),
607             null,
608             null,
609             null,
610             null,
611             null,
612             null,
613         );
614     }
615     comptime {
616         @stardustClaim(
617             alloc_phase.capacity.witness(Storage, "gui_text_fallback_reuse"),
618             null,
619             null,
620             null,
621             null,
622             null,
623             null,
624         );
625     }
626     comptime {
627         @stardustClaim(
628             alloc_phase.capacity.witness(Storage, "gui_text_fallback_sealed_transitive_risk"),
629             null,
630             null,
631             null,
632             null,
633             null,
634             null,
635         );
636     }
637     comptime {
638         @stardustClaim(
639             alloc_phase.capacity.witness(Storage, "gui_text_fallback_sealed_foreign_risk"),
640             null,
641             null,
642             null,
643             null,
644             null,
645             null,
646         );
647     }
648     const segment = filigree.FallbackSegment{
649         .source = .{ .start = 0, .end = 1 },
650         .glyphs = .{ .start = 0, .end = 1 },
651         .candidate_index = 1,
652         .missing_everywhere = false,
653     };
654     const placement = try payloadPlacement(0, 1, 1);
655     var storage = try activatedStorage(std.testing.allocator, .{ .max_source_units = 1, .cache_entries = 1, .cache_payload_bytes = placement.end });
656     defer storage.deinit(std.testing.allocator);
657     const key = Key{ .content = "x", .primary_identity = 11, .fallback_identity = 22 };
658     const stored = try storage.store(key, &.{segment});
659     const stored_pointer = stored.ptr;
660     const prior = stored[0];
661     const prior_status = storage.status();
662 
663     try std.testing.expectError(error.SourceUnitCapacityExceeded, storage.segmentList(2));
664     try std.testing.expectError(error.SegmentCapacityExceeded, storage.store(key, &.{ segment, segment }));
665     try std.testing.expectError(error.CacheEntryCapacityExceeded, storage.store(.{ .content = "y", .primary_identity = 11, .fallback_identity = 22 }, &.{segment}));
666     try std.testing.expectEqual(prior, storage.lookup(key).?[0]);
667     try std.testing.expectEqual(stored_pointer, storage.lookup(key).?.ptr);
668     try std.testing.expectEqual(prior_status.entries, storage.status().entries);
669     try std.testing.expectEqual(prior_status.payload_bytes, storage.status().payload_bytes);
670 
671     storage.reset();
672     try std.testing.expectEqual(@as(usize, 0), storage.status().entries);
673     try std.testing.expectEqual(@as(u64, 1), storage.status().rollovers);
674     _ = try storage.store(key, &.{segment});
675 }
676 
677 test "fallback segment payload max plus one rejects without cache mutation" {
678     const segment = filigree.FallbackSegment{
679         .source = .{ .start = 0, .end = 1 },
680         .glyphs = .{ .start = 0, .end = 1 },
681         .candidate_index = 1,
682         .missing_everywhere = false,
683     };
684     const placement = try payloadPlacement(0, 1, 1);
685     var storage = try activatedStorage(std.testing.allocator, .{ .max_source_units = 2, .cache_entries = 2, .cache_payload_bytes = placement.end });
686     defer storage.deinit(std.testing.allocator);
687     const key = Key{ .content = "x", .primary_identity = 11, .fallback_identity = 22 };
688     const prior = try storage.store(key, &.{segment});
689     const pointer = prior.ptr;
690     try std.testing.expectError(
691         error.CachePayloadCapacityExceeded,
692         storage.store(.{ .content = "yy", .primary_identity = 11, .fallback_identity = 22 }, &.{segment}),
693     );
694     try std.testing.expectEqual(pointer, storage.lookup(key).?.ptr);
695     try std.testing.expectEqual(@as(usize, 1), storage.status().entries);
696     try std.testing.expectEqual(placement.end, storage.status().payload_bytes);
697 }
698 
699 test "fallback segment storage is exported through paint root" {
700     comptime {
701         @stardustClaim(alloc_phase.capacity.witness(Storage, "gui_text_fallback_root"), null, null, null, null, null, null);
702     }
703     try std.testing.expect(@import("root.zig").TextFallbackStorage == Storage);
704 }