invention purchase, at least, how I think they should work

This commit is contained in:
Devin Zuczek
2026-08-05 14:58:55 -04:00
parent b82a5e1dc0
commit a986d012f5
7 changed files with 379 additions and 51 deletions
+82
View File
@@ -37,6 +37,25 @@ export const MAX_CLUB_DESCRIPTION_LENGTH = 512
export const MAX_EVENT_NAME_LENGTH = 64
export const MAX_EVENT_DESCRIPTION_LENGTH = 512
/**
* Invention limits. A name is a title a player types into the invention-save box and
* reads back in a browse tile, so it allows the punctuation a title needs — but
* nothing else, since it is also what invention search matches on. The minimum is real:
* one- and two-character names are unsearchable and unreadable in a tile, and the client
* offers `Untitled` rather than an empty box.
*/
export const MIN_INVENTION_NAME_LENGTH = 3
export const MAX_INVENTION_NAME_LENGTH = 24
export const MAX_INVENTION_DESCRIPTION_LENGTH = 512
/**
* One invention tag. Short and letters-only because tags are a controlled vocabulary the
* browse chips are derived from (see `getInventionTagFilters`) — a tag with digits,
* punctuation or spaces makes a chip nobody else will ever type again. Tags are stored
* lowercased, so the rule is checked against the normalized form, not what was typed.
*/
export const MAX_INVENTION_TAG_LENGTH = 15
/**
* Length in code points rather than UTF-16 units, so an emoji or other astral character
* counts once instead of twice — the way a player counts what they typed.
@@ -74,6 +93,69 @@ export function nameRejection(value: string, label: string, max: number): string
return null
}
/**
* Letters, digits, spaces, dashes and colons — the title charset. Wider than
* `NAME_PATTERN` because an invention is a thing with a name ("Grappling Hook v2",
* "Speed-Boost Pad"), not an identifier someone types into a sign-in box. Still no
* arbitrary Unicode, for the same homoglyph reasons.
*
* The colon is not decorative: an invention the player never named is called after the
* moment it was saved (`071126 13:10:50`), generated by the CLIENT, so a rule without it
* would refuse every unnamed save the game makes. The dash stays last in the class so it
* reads as a literal rather than a range.
*/
const INVENTION_NAME_PATTERN = /^[A-Za-z0-9 :-]+$/
/** Lowercase letters only — the normalized form a tag is stored in. */
const INVENTION_TAG_PATTERN = /^[a-z]+$/
/**
* Why a player-supplied invention name is unacceptable, or `null` when it's fine.
*
* Callers pass the TRIMMED name: leading and trailing spaces are the player's typing,
* not part of what they named the thing, and counting them toward the minimum would let
* `" a "` through.
*/
export function inventionNameRejection(value: string): string | null {
if (glyphLength(value) < MIN_INVENTION_NAME_LENGTH) {
return `Invention names must be at least ${MIN_INVENTION_NAME_LENGTH} characters.`
}
if (glyphLength(value) > MAX_INVENTION_NAME_LENGTH) {
return `Invention names can be at most ${MAX_INVENTION_NAME_LENGTH} characters.`
}
if (!INVENTION_NAME_PATTERN.test(value)) {
return 'Invention names can only contain letters, numbers, spaces, dashes and colons.'
}
return null
}
/**
* Why an invention description is unacceptable, or `null` when it's fine. Length only —
* a description is prose, so nothing is refused for the characters it's made of, and an
* empty one is fine (it's how a creator clears the field).
*/
export function inventionDescriptionRejection(value: string): string | null {
if (glyphLength(value) > MAX_INVENTION_DESCRIPTION_LENGTH) {
return `Invention descriptions can be at most ${MAX_INVENTION_DESCRIPTION_LENGTH} characters.`
}
return null
}
/**
* Why an invention tag is unacceptable, or `null` when it's fine. Pass the NORMALIZED
* tag (trimmed and lowercased, as `setInventionTags` stores it) — checking what was typed
* instead would refuse `Racing` for a capital that never reaches the database.
*/
export function inventionTagRejection(value: string): string | null {
if (value.length > MAX_INVENTION_TAG_LENGTH) {
return `Invention tags can be at most ${MAX_INVENTION_TAG_LENGTH} characters.`
}
if (!INVENTION_TAG_PATTERN.test(value)) {
return 'Invention tags can only contain letters.'
}
return null
}
/**
* Whether a supplied email is one worth storing — RFC 5321/5322 syntax, via `isemail`.
*