{
	"schemaVersion": 1,
	"title": "Substring Search Benchmark Report",
	"description": "Synthetic large-catalog search benchmarks for the kind of item, tooltip, mod-name, and tag searching players use in recipe viewers.",
	"benchmarkClass": "net.mezzdev.substringsearch.benchmark.LargeCatalogSearchBenchmark",
	"project": {
		"groupId": "net.mezzdev",
		"artifactId": "substring-search-benchmarks",
		"version": "0.1.0-SNAPSHOT",
		"javaRelease": "25"
	},
	"toolchain": {
		"jmhVersion": "1.37",
		"scoreErrorConfidence": 0.95,
		"modDevGradleVersion": "2.0.141",
		"neoFormVersion": "26.2-2"
	},
	"itemCounts": [
		100000
	],
	"typedSearch": {
		"terms": [
			"reinforced copper ingot",
			"polished redstone nugget",
			"tempered sapphire ingot",
			"gilded topaz nugget",
			"raw steel ingot",
			"cut glass nugget",
			"smooth quartz ingot",
			"silent slate nugget",
			"Aster Works",
			"Beacon Works",
			"Cinder Works",
			"Delta Works",
			"bench:ingots/copper_ingot",
			"bench:tools/copper_ingot",
			"stat 0 detail",
			"dense nickel ingot"
		],
		"typedCharacterCounts": [
			1,
			2,
			3,
			4,
			5,
			6,
			7,
			8,
			9,
			10,
			11
		],
		"queriesByLength": {
			"1": [
				"r",
				"p",
				"t",
				"g",
				"r",
				"c",
				"s",
				"s",
				"A",
				"B",
				"C",
				"D",
				"b",
				"b",
				"s",
				"d"
			],
			"2": [
				"re",
				"po",
				"te",
				"gi",
				"ra",
				"cu",
				"sm",
				"si",
				"As",
				"Be",
				"Ci",
				"De",
				"be",
				"be",
				"st",
				"de"
			],
			"3": [
				"rei",
				"pol",
				"tem",
				"gil",
				"raw",
				"cut",
				"smo",
				"sil",
				"Ast",
				"Bea",
				"Cin",
				"Del",
				"ben",
				"ben",
				"sta",
				"den"
			],
			"4": [
				"rein",
				"poli",
				"temp",
				"gild",
				"raw ",
				"cut ",
				"smoo",
				"sile",
				"Aste",
				"Beac",
				"Cind",
				"Delt",
				"benc",
				"benc",
				"stat",
				"dens"
			],
			"5": [
				"reinf",
				"polis",
				"tempe",
				"gilde",
				"raw s",
				"cut g",
				"smoot",
				"silen",
				"Aster",
				"Beaco",
				"Cinde",
				"Delta",
				"bench",
				"bench",
				"stat ",
				"dense"
			],
			"6": [
				"reinfo",
				"polish",
				"temper",
				"gilded",
				"raw st",
				"cut gl",
				"smooth",
				"silent",
				"Aster ",
				"Beacon",
				"Cinder",
				"Delta ",
				"bench:",
				"bench:",
				"stat 0",
				"dense "
			],
			"7": [
				"reinfor",
				"polishe",
				"tempere",
				"gilded ",
				"raw ste",
				"cut gla",
				"smooth ",
				"silent ",
				"Aster W",
				"Beacon ",
				"Cinder ",
				"Delta W",
				"bench:i",
				"bench:t",
				"stat 0 ",
				"dense n"
			],
			"8": [
				"reinforc",
				"polished",
				"tempered",
				"gilded t",
				"raw stee",
				"cut glas",
				"smooth q",
				"silent s",
				"Aster Wo",
				"Beacon W",
				"Cinder W",
				"Delta Wo",
				"bench:in",
				"bench:to",
				"stat 0 d",
				"dense ni"
			],
			"9": [
				"reinforce",
				"polished ",
				"tempered ",
				"gilded to",
				"raw steel",
				"cut glass",
				"smooth qu",
				"silent sl",
				"Aster Wor",
				"Beacon Wo",
				"Cinder Wo",
				"Delta Wor",
				"bench:ing",
				"bench:too",
				"stat 0 de",
				"dense nic"
			],
			"10": [
				"reinforced",
				"polished r",
				"tempered s",
				"gilded top",
				"raw steel ",
				"cut glass ",
				"smooth qua",
				"silent sla",
				"Aster Work",
				"Beacon Wor",
				"Cinder Wor",
				"Delta Work",
				"bench:ingo",
				"bench:tool",
				"stat 0 det",
				"dense nick"
			],
			"11": [
				"reinforced ",
				"polished re",
				"tempered sa",
				"gilded topa",
				"raw steel i",
				"cut glass n",
				"smooth quar",
				"silent slat",
				"Aster Works",
				"Beacon Work",
				"Cinder Work",
				"Delta Works",
				"bench:ingot",
				"bench:tools",
				"stat 0 deta",
				"dense nicke"
			]
		}
	},
	"reportSections": [
		{
			"id": "build-time",
			"name": "Build Time Rankings",
			"metric": "startup or reload time spent preparing search",
			"unit": "ms/build",
			"lowerIsBetter": true,
			"description": "Measures how long it takes to prepare the search index after a large item list is available. Lower values mean a recipe viewer can become searchable sooner after game startup, resource reloads, or data reloads.",
			"benchmarkGroups": [
				"build"
			]
		},
		{
			"id": "typed-search-time",
			"name": "Typed Search Time",
			"metric": "time to process one search-box update",
			"unit": "ms/query",
			"lowerIsBetter": true,
			"description": "Measures the cost of one search-box update as a player types more characters. Lower values mean less input lag while filtering a very large item list.",
			"benchmarkGroups": [
				"typed-search"
			]
		},
		{
			"id": "allocation-100k",
			"name": "Allocation Rankings At 100k",
			"metric": "temporary garbage created while preparing search",
			"unit": "bytes/build",
			"lowerIsBetter": true,
			"description": "Measures temporary objects allocated while building the index for a 100,000-item catalog. Lower values reduce GC pressure and the chance of startup or reload stutter.",
			"benchmarkGroups": [
				"build"
			]
		},
		{
			"id": "typed-allocation-100k",
			"name": "Typed Search Allocations At 100k",
			"metric": "temporary garbage created per search-box update",
			"unit": "bytes/query",
			"lowerIsBetter": true,
			"description": "Measures temporary objects allocated for each search-box update against a 100,000-item catalog. Lower values reduce GC pressure while a player is typing.",
			"benchmarkGroups": [
				"typed-search"
			]
		},
		{
			"id": "retained-memory",
			"name": "Retained Memory",
			"metric": "additional RAM kept by the ready-to-use search solution",
			"unit": "bytes",
			"lowerIsBetter": true,
			"description": "Estimates how much extra JVM heap the completed all-defaults search solution keeps after loading, above a synthetic baseline item list that is already live. The benchmark uses one indexed result value per search string, matching JEI's separate search-tree shape. Searchable strings are generated after the baseline, so raw or encoded string storage retained by the index counts as additional search memory. The Lucene adapter uses heap-backed ByteBuffersDirectory in this harness; mmap-backed Lucene storage would need a separate mapped-file metric. Lower values leave more memory for the rest of the game and modpack.",
			"benchmarkGroups": [
				"retained-memory"
			]
		}
	],
	"implementations": [
		{
			"id": "generalized-suffix-tree",
			"name": "Suffix tree",
			"authors": "Abahgat, mezz",
			"version": "1.3.0",
			"source": "pkg:maven/net.mezzdev:suffixtree@1.3.0",
			"sourceUrl": "https://github.com/mezz/generalized-suffix-tree",
			"note": "",
			"order": 0
		},
		{
			"id": "abahgat-suffix-tree",
			"name": "Suffix tree",
			"authors": "Abahgat",
			"version": "2.0.0",
			"source": "pkg:maven/com.abahgat:suffixtree@2.0.0",
			"sourceUrl": "https://github.com/abahgat/suffixtree",
			"note": "",
			"order": 1
		},
		{
			"id": "baked-substring-index",
			"name": "Baked substring index",
			"authors": "mezz",
			"version": "0.1.0",
			"source": "pkg:maven/net.mezzdev:baked-substring-index@0.1.0",
			"sourceUrl": "https://github.com/mezz/baked-substring-index",
			"note": "",
			"order": 2
		},
		{
			"id": "baked-suffix-array-index",
			"name": "Baked suffix array index",
			"authors": "mezz",
			"version": "0.1.0",
			"source": "pkg:maven/net.mezzdev:baked-suffix-array-index@0.1.0",
			"sourceUrl": "https://github.com/mezz/baked-suffix-array-index",
			"note": "",
			"order": 3
		},
		{
			"id": "lucene",
			"name": "Lucene wildcard index",
			"authors": "Apache Lucene",
			"version": "10.5.0",
			"source": "pkg:maven/org.apache.lucene:lucene-core@10.5.0",
			"sourceUrl": "https://lucene.apache.org/",
			"note": "Benchmark stores each corpus line as one exact Lucene StringField term in heap-backed ByteBuffersDirectory and searches with a leading/trailing WildcardQuery. It does not precompute suffixes or substrings; this trades lower retained heap for query-time term scanning. An MMapDirectory variant would move segment bytes outside JVM heap and would need a separate mapped-file metric.",
			"order": 4
		},
		{
			"id": "minecraft-suffix-array",
			"name": "Suffix array",
			"authors": "Minecraft",
			"version": "1.12-pre6 to 26.2",
			"source": "Minecraft 1.12-pre6 to 26.2 SuffixArray via NeoForm 26.2-2",
			"sourceUrl": "https://github.com/neoforged/NeoForm",
			"note": "Benchmark uses net.minecraft.client.searchtree.SuffixArray from NeoForm 26.2-2. A manual all-versions binary search found the SuffixArray bytecode signature from Minecraft 1.12-pre6 through 26.2; the first release with the signature is 1.12.",
			"order": 5
		}
	],
	"benchmarks": [
		{
			"id": "buildAllDefaultsIndex",
			"name": "Build: All Defaults",
			"description": "Prepares the full default search index: item names, tooltip text, mod names, and tags. This is the closest benchmark to the total search setup cost players feel after loading or reloading a large modpack.",
			"group": "build",
			"order": 10,
			"legacy": false
		},
		{
			"id": "buildItemNamesIndex",
			"name": "Build: Item Names",
			"description": "Prepares search for the item names players see in the item list. This is the basic name-search part of startup or reload cost.",
			"group": "build",
			"order": 20,
			"legacy": false
		},
		{
			"id": "buildTooltipLinesIndex",
			"name": "Build: Tooltip Lines",
			"description": "Prepares search for tooltip text such as hold-Shift details, stats, and descriptions. Tooltip text is large and repetitive, so it can dominate startup time, temporary garbage, and memory.",
			"group": "build",
			"order": 30,
			"legacy": false
		},
		{
			"id": "buildModNamesIndex",
			"name": "Build: Mod Names",
			"description": "Prepares search for the mod or source name shown for each item. This corpus is smaller than tooltips, but it shows the fixed cost of supporting mod-name searches.",
			"group": "build",
			"order": 40,
			"legacy": false
		},
		{
			"id": "buildTagsIndex",
			"name": "Build: Tags",
			"description": "Prepares search for tag-style strings players use to find item groups such as ingots, ores, or storage blocks. More indexed text means more startup work and memory.",
			"group": "build",
			"order": 50,
			"legacy": false
		},
		{
			"id": "searchAllDefaultsTypedCatalogPrefix",
			"name": "Typed Search: Default Corpus Prefix",
			"description": "Simulates a player typing in the search box after the index is ready. Each step searches prefixes from many item-like words, so lower values mean the filtered item list updates with less typing lag.",
			"group": "typed-search",
			"order": 200,
			"legacy": false
		},
		{
			"id": "retainedApproxAllDefaultsIndexBytes",
			"name": "Retained Memory: All Defaults",
			"description": "Keeps a synthetic JEI item-list baseline alive, then generates one indexed result value per search string, builds the full default search index, lets the input corpus become unreachable, and estimates the additional JVM heap retained by the ready search solution. This models JEI's separate search trees for different searchable string types, where each tree entry points to one item. If an index retains raw searchable strings or encoded equivalents such as Lucene term/postings data, that memory counts as search-owned memory. The Lucene benchmark uses heap-backed ByteBuffersDirectory; an mmap-backed Lucene variant would need a separate mapped-file metric. Lower values mean the search feature leaves more RAM for the game, resource packs, and other mods. Auxiliary counters break down heap delta, object graph size, and identifiable implementation-specific memory components. This retained-memory number is approximate.",
			"group": "retained-memory",
			"order": 300,
			"legacy": false
		}
	]
}
