xiaohongshu
xiaohongshu
adding-mod-parsers
Use when adding new mod parsers to convert game mod strings to typed Mod objects - guides the template-based parsing pattern (project)
Full skill instructions
The mod parser converts raw mod strings (e.g., "+10% all stats") into typed Mod objects used by the calculation engine. It uses a template-based system for pattern matching.
| Purpose | File Path |
|---|---|
| Mod type definitions | src/tli/mod.ts |
| Parser templates | src/tli/mod-parser/templates.ts |
| Enum registrations | src/tli/mod-parser/enums.ts |
| Calculation handlers | src/tli/calcs/offense.ts |
| Tests | src/tli/mod-parser.test.ts |
Look in src/tli/mod.ts under ModDefinitions. If the mod type doesn't exist, add it:
interface ModDefinitions {
// ... existing types ...
NewModType: { value: number; someField: string };
}
templates.tsTemplates use a DSL for pattern matching. Do not add comments to templates.ts - the template string itself is self-documenting.
t("{value:dec%} all stats").output("StatPct", (c) => ({
value: c.value,
statModType: "all" as const,
})),
t("{value:dec%} {statModType:StatWord}")
.enum("StatWord", StatWordMapping)
.output("StatPct", (c) => ({ value: c.value, statModType: c.statModType })),
t("{value:dec%} [additional] [{modType:DmgModType}] damage").output("DmgPct", (c) => ({
value: c.value,
dmgModType: c.modType ?? "global",
addn: c.additional !== undefined,
})),
t("{value:dec%} attack and cast speed").outputMany([
spec("AspdPct", (c) => ({ value: c.value, addn: false })),
spec("CspdPct", (c) => ({ value: c.value, addn: false })),
]),
Template capture types:
| Type | Matches | Example Input → Output |
|---|---|---|
{name:int} | Unsigned integer | "5" → 5 |
{name:dec} | Unsigned decimal | "21.5" → 21.5 |
{name:int%} | Unsigned integer percent | "30%" → 30 |
{name:dec%} | Unsigned decimal percent | "96%" → 96 |
{name:+int} | Signed integer (requires + or -) | "+5" → 5, "-3" → -3 |
{name:+dec} | Signed decimal (requires + or -) | "+21.5" → 21.5 |
{name:+int%} | Signed integer percent | "+30%" → 30, "-15%" → -15 |
{name:+dec%} | Signed decimal percent | "+96%" → 96 |
{name:EnumType} | Enum lookup | {dmgType:DmgChunkType} |
Signed vs Unsigned Types:
dec%, int) when input does NOT start with + or - (e.g., "8% additional damage applied to Life")+dec%, +int) when input STARTS with + or - (e.g., "+25% additional damage")Optional syntax:
[additional] - Optional literal, sets c.additional?: true[{modType:DmgModType}] - Optional capture, sets c.modType?: DmgModType{(effect|damage)} - Alternation (regex-style)If you need custom word → value mapping, add to enums.ts:
export const StatWordMapping: Record<string, string> = {
strength: "str",
dexterity: "dex",
intelligence: "int",
};
registerEnum("StatWord", ["strength", "dexterity", "intelligence"]);
offense.ts (if new mod type)If you added a new mod type, add handling in calculateOffense() or relevant helper:
case "NewModType": {
break;
}
For existing mod types with new variants (like adding statModType: "all"), update existing handlers to also filter for the new variant:
const flat = sumByValue(
statMods.filter((m) => m.statModType === statType || m.statModType === "all"),
);
Add test cases in src/tli/mod_parser.test.ts:
test("parse percentage all stats", () => {
const result = parseMod("+10% all stats");
expect(result).toEqual([
{
type: "StatPct",
statModType: "all",
value: 10,
},
]);
});
pnpm test src/tli/mod_parser.test.ts
pnpm typecheck
pnpm check
IMPORTANT: More specific patterns must come before generic ones in allParsers array.
// Good: specific before generic
t("{value:dec%} all stats").output(...), // Specific
t("{value:dec%} {statModType:StatWord}").output(...), // Generic
// Bad: generic would match first and fail on "all stats"
Input: "+10% all stats" (starts with +)
t("{value:+dec%} all stats").output("StatPct", (c) => ({
value: c.value,
statModType: "all" as const,
})),
Input: "8% additional damage applied to Life" (no sign)
t("{value:dec%} additional damage applied to life").output("DmgPct", (c) => ({
value: c.value,
dmgModType: "global" as const,
addn: true,
})),
Input: "+40% damage if you have Blocked recently"
t("{value:+dec%} damage if you have blocked recently").output("DmgPct", (c) => ({
value: c.value,
dmgModType: "global" as const,
addn: false,
cond: "has_blocked_recently" as const,
})),
Input: "Deals +1% additional damage to an enemy for every 2 points of Frostbite Rating the enemy has"
Note: The + appears AFTER "deals", so use {value:+dec%}:
t("deals {value:+dec%} additional damage to an enemy for every {amt:int} points of frostbite rating the enemy has")
.output("DmgPct", (c) => ({
value: c.value,
dmgModType: "global" as const,
addn: true,
per: { stackable: "frostbite_rating" as const, amt: c.amt },
})),
Input: "+6% attack and cast speed"
t("{value:+dec%} [additional] attack and cast speed").outputMany([
spec("AspdPct", (c) => ({ value: c.value, addn: c.additional !== undefined })),
spec("CspdPct", (c) => ({ value: c.value, addn: c.additional !== undefined })),
]),
Input: "+166 Max Mana"
t("{value:+dec} max mana").output("MaxMana", (c) => ({ value: c.value })),
Input: "Energy Shield starts to Charge when Blocking"
Use outputNone() when a mod string should be recognized (not flagged as unparsed) but has no effect on calculations:
t("energy shield starts to charge when blocking").outputNone(),
| Mistake | Fix |
|---|---|
Using dec% for input with + prefix | Use +dec% for inputs like "+25% damage" |
Using +dec% for input without sign | Use dec% for inputs like "8% damage applied to life" |
| Template doesn't match input case | Templates are matched case-insensitively; input is normalized to lowercase |
Missing as const on string literals | Add as const for type narrowing: statModType: "all" as const |
| Handler doesn't account for new variant | Update offense.ts to handle new values (e.g., statModType === "all") |
| Generic template before specific | Move specific templates earlier in allParsers array |
Raw string: "+10% all stats"
↓ normalize (lowercase, trim)
"10% all stats"
↓ template matching (allParsers)
{ type: "StatPct", value: 10, statModType: "all" }
↓ calculateStats() in offense.ts
Applied to str, dex, int calculations
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience