diff --git a/plan/forces.md b/plan/forces.md index a1b14f5..d0d6820 100644 --- a/plan/forces.md +++ b/plan/forces.md @@ -118,6 +118,17 @@ editor — porting MegaMekLab is [declined](declined.md), and model as two fields and a display name that folds a Clan design's second name in; the lookup has to match all three, because a player types `nova prime` and means `Black Hawk (Nova) Prime`. +- [ ] **Make `era` mean an era.** The Hangar's query language leaves the key + unclaimed on purpose. A year is when a design was introduced and the + index carries it; an era is a period a design was available in, and a + 2750 machine belongs to the Star League and to the Dark Age both. + Both halves of the answer are in the release already: the periods and + their bounds are `data/universe/eras.xml`, fourteen of them from + Pre-Spaceflight to ilClan, and availability is + `megamek.common.TechAdvancement`, which holds introduction, extinction + and reintroduction dates rather than one year. Neither is in the index, + so the key is listed as coming soon rather than quietly answered with + `year`, and the force target's field is a Year. - [ ] **Filter with the same predicate the server uses**, compiled to wasm — [unit-search](unit-search.md)'s open decision, and this screen is the consumer that makes it due. diff --git a/web/scripts/hangar.test.mjs b/web/scripts/hangar.test.mjs index a665879..8c26e70 100644 --- a/web/scripts/hangar.test.mjs +++ b/web/scripts/hangar.test.mjs @@ -424,6 +424,7 @@ test("the filter reference says what is not supported yet, and marks it", async // does not carry. const hangar = await readFile(`${web}src/screens/hangar.ts`, "utf8"); for (const missing of [ + "era", "equipment", "quirks", "damage", @@ -432,7 +433,7 @@ test("the filter reference says what is not supported yet, and marks it", async ]) { assert.match( hangar, - new RegExp(`soon\\("${missing}"`), + new RegExp(`soon\\(\\s*"${missing}"`), `${missing} is not listed as unsupported`, ); } @@ -445,6 +446,34 @@ test("the filter reference says what is not supported yet, and marks it", async assert.equal(parseQuery('equipment:"ER PPC"').filters.length, 0); }); +test("era is not another word for year, and is not accepted", () => { + // A year is when a design was introduced; an era is a period it was + // available in, and a 2750 design belongs to Star League and to Dark Age + // both. Taking `era` as `year` would answer a different question quietly. + assert.equal(parseQuery("era:jihad").filters.length, 0); + assert.equal(parseQuery("era<=3025").filters.length, 0); + assert.ok(!FILTER_KEYS.includes("era"), "era is still claimed as a key"); + assert.deepEqual(parseQuery("year<=3025").filters, [ + { key: "year", op: "<=", value: "3025" }, + ]); +}); + +test("weight asks the same question as mass", () => { + const light = { name: "Locust LCT-1V", chassis: "Locust", mass: 20 }; + const heavy = { name: "Atlas AS7-D", chassis: "Atlas", mass: 100 }; + assert.deepEqual( + search([light, heavy], "weight>=70").map((u) => u.chassis), + ["Atlas"], + ); + for (const key of ["mass", "tons", "tonnage", "weight"]) { + assert.equal( + search([light, heavy], `${key}<=20`).length, + 1, + `${key} does not read tonnage`, + ); + } +}); + test("the reference explains every key the box accepts", async () => { // The list under Filters is the only place a key's meaning is written down, // so a key the parser takes and the dialog omits is a key nobody can learn. diff --git a/web/src/screens/hangar.ts b/web/src/screens/hangar.ts index d4fa2af..b37af05 100644 --- a/web/src/screens/hangar.ts +++ b/web/src/screens/hangar.ts @@ -199,20 +199,17 @@ export function hangarScreen(): Node[] { "Bare words match a design's name. A term filters one column: a key, then one of : = < <= > >=, then a value. Terms combine with and.", }), el("ul", { className: "hangar-help-examples" }, [ - example( - "atlas", - "39 designs: the Atlas, the Atlas II and the Atlas III", - ), + example("atlas", "39 designs: every Atlas, Atlas II and Atlas III"), example( "hawk", - "193, because a bare word reads the model as well as the chassis", + "193: a Phoenix Hawk, a Black Hawk and a Battle Hawk all answer to it", ), example( 'chassis:"black hawk"', - "33: the Black Hawks alone, quoted because the name has a space in it", + "33: the Black Hawks, and no Phoenix Hawk. Quoted, because the name has a space in it", ), example( - "class:heavy jump>0 era<=3025", + "class:heavy jump>0 year<=3025", "heavies that jump, introduced by 3025", ), ]), @@ -247,8 +244,8 @@ export function hangarScreen(): Node[] { ), filter("bv", "battle value.", "bv<=1500"), filter("cost", "the price in C-bills.", "cost<5000000"), - filter("mass, tons, tonnage", "tonnage.", "mass>=70"), - filter("era, year", "the year the design was introduced.", "era<=3025"), + filter("mass, tons, tonnage, weight", "tonnage.", "mass>=70"), + filter("year", "the year the design was introduced.", "year<=3025"), filter( "armor, armour", "armour points over the whole machine.", @@ -274,6 +271,11 @@ export function hangarScreen(): Node[] { ]), el("h3", { textContent: "Not yet" }), el("ul", { className: "hangar-help-soon" }, [ + soon( + "era", + "era:jihad", + "whether a design was available in a named era, which a year of introduction cannot answer", + ), soon("equipment", 'equipment:"ER PPC"', "what a design carries"), soon("quirks", "quirks:battlefists", "MegaMek's per-design quirks"), soon("damage", "damage9>=20", "damage at a range band"), diff --git a/web/src/units.ts b/web/src/units.ts index 767202c..495b5d8 100644 --- a/web/src/units.ts +++ b/web/src/units.ts @@ -212,17 +212,23 @@ export interface Query { * The columns a filter can name, and what each one is called in a query. * * Aliased where the word a player reaches for is not the column's name: - * `era` and `year` are the same question, and so are `mass`, `tons` and - * `tonnage`. + * `mass`, `tons`, `tonnage` and `weight` are one question, and so are `armor` + * and `armour`. + * + * `era` is deliberately absent. It is not another word for `year`: a year is + * when a design was introduced, an era is a period it was available in, and + * a design introduced in 2750 is a Star League machine and a Dark Age one + * both. Answering that needs the introduction, extinction and reintroduction + * dates the index does not carry, so the key is unclaimed rather than wrong. */ const FIELDS: Record = { year: { column: "year", kind: "number" }, - era: { column: "year", kind: "number" }, bv: { column: "bv", kind: "number" }, mass: { column: "mass", kind: "number" }, tons: { column: "mass", kind: "number" }, tonnage: { column: "mass", kind: "number" }, + weight: { column: "mass", kind: "number" }, walk: { column: "walk_mp", kind: "number" }, jump: { column: "jump_mp", kind: "number" }, armor: { column: "total_armor", kind: "number" },