lpcdoc
LPCDoc comment block generation guide. Consult when writing or updating documentation headers for LPC functions, classes, and modules.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
LPCDoc comment block generation guide. Consult when writing or updating documentation headers for LPC functions, classes, and modules.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Review standard for Oxidus changes — what qualifies as a finding, how scope is bounded, the evidence required before asserting a failure mode, and the LPC/FluffOS semantics most often mistaken for defects. Consult before reviewing, critiquing, or reporting findings on any LPC change, diff, or pull request.
LPC coding style and formatting conventions for this project. Consult when writing or modifying any LPC code — covers spacing, indentation, braces, naming, control flow, and idiomatic patterns.
Create and modify daemons for Oxidus. Covers inheriting STD_DAEMON, the setup chain, persistence (setPersistent/saveData/restore_data), preloading, SWAP_D for reload-safe data, logging with EXT_LOG, and the teardown chain.
LPML (LPC Markup Language) specification reference. Consult when reading, writing, editing, or validating .lpml files. Covers syntax, data types, string concatenation, spacey keys, file includes, multiline folding, and all extensions over JSON5.
Create and modify NPCs and monsters for Oxidus. Covers code-based NPCs (STD_NPC), data-driven monsters (virtual_setup, LPML), set_level, race modules, body parts, heartbeat optimization, combat memory, loot/coin tables, utility-AI decisions, and the virtual compile flow.
Understand and work with the boon/curse (buff/debuff) system in Oxidus. Covers boon and curse application, class/type structure, stacking, expiration, querying effective values, and integration with attributes, vitals, and skills.
| name | lpcdoc |
| description | LPCDoc comment block generation guide. Consult when writing or updating documentation headers for LPC functions, classes, and modules. |
This document provides instructions for generating LPCDoc comment blocks for LPC source code. Follow these guidelines to create accurate, consistent, and useful documentation.
public, protected, and private functions require an
LPCDoc block. Lifecycle entry points and forward declarations
are exempt (see "What Not to Document" below). The LSP relies on
@param types to resolve parameters in helpers, so private
helpers are not optional in this project.@type annotation gives the LSP shape information it cannot
already infer. Document globals whose type is an object
(STD_* macros or file paths), a structured mapping
(([ string: int ])), a class/struct, an array of any of those,
or a union of such types. Plain primitives (int, string,
float) and untyped containers do not need an LPCDoc block —
the type is already obvious from the declaration and adds no
LSP value. The point of documenting variables is to feed the
LSP, not to narrate the obvious.@param and @returns. The {type}
annotation is required, not optional — every @param must include
a type and every @returns must include a type.When working on a file, update any existing comments and headers that do not match these conventions. This includes:
@file format)Do not go out of your way to document the entire file — but if you encounter non-conforming documentation while working, fix it.
LPCDoc comments must be placed directly before the code element they document and follow this structure:
/**
* Description of the element (function, variable, etc.)
*
* Additional details if needed, forming a complete paragraph.
*
* @tags and other structured elements
*/
Key points:
/** and end with */* (space, asterisk, space) *) between the description and
the first tag. This is mandatory even for short one-line descriptions.For functions, document:
Example:
/**
* Calculates the total damage based on attack power and defence.
*
* @param {int} attack - The attack power value
* @param {int} defence - The defence value
* @returns {int} The calculated damage amount
* @throws If either value is negative
*/
int calculateDamage(int attack, int defence) {
// Implementation
}
For variables, document:
Example:
/**
* Maximum health points allowed for any player character.
*
* @type {int}
*/
int MAX_PLAYER_HP = 100;
@paramDocuments a function parameter.
Syntax: @param {type} name - Description
A hyphen (-) is required between the parameter name and its
description.
/**
* @param {int} attack - The attack power value.
* @param {string} target - The name of the target.
*/
Wrap the parameter name in brackets to indicate it is optional.
/**
* @param {mapping} [options] - Optional configuration settings.
*/
Append =value inside the brackets to document a default.
/**
* @param {string} [which="door"] - The specific door to unlock.
*/
int unlock(string which) {
which = which || "door";
}
In FluffOS, parameters passed by reference can be documented with &.
/**
* @param {int} &value - A reference to an integer that will be
* modified.
*/
void increment(int ref value) {
value++;
}
For a varargs rest parameter (declared type name... or
type *name...), the variadic marker belongs inside the type
braces, not after the parameter name. The LPC language server only
recognises ... as a variadic marker when it appears within the
{type} expression — a ... written after the name is silently
swallowed as comment text and conveys nothing to the LSP.
Always use the LPC-style trailing form {type...} — the marker mirrors
the declaration, where ... follows the type. This is the normative
form for this project:
/**
* @param {mixed...} arg - Optional trailing arguments forwarded to
* the callback.
*/
void schedule(string func, mixed arg...) {
// ...
}
The JS-style leading form {...type} is also accepted by the parser
and appears in older code, but do not use it in new documentation;
convert it to {type...} when you encounter it.
Do not write the marker after the name — this does not register as variadic:
/**
* @param {mixed} arg... - WRONG: the ... is treated as description
* text, not a variadic marker.
*/
@returnsDocuments the return value of a function.
Syntax: @returns {type} Description
Do not use a hyphen between the type and the description.
/**
* @returns {int} The calculated damage amount.
*/
When a function may return different types depending on conditions, use a union type.
/**
* @returns {object | string} The entity if found, or an error
* message.
*/
A special form of @returns enables type narrowing in conditional
branches. Instead of documenting a return type, it declares that the
function acts as a type guard for one of its parameters.
Syntax: @returns {paramName is type}
Where paramName is the name of a parameter in the function signature
and type is the type it should be narrowed to when the function
returns a truthy value.
/**
* @param {mixed} arg
* @returns {arg is string}
*/
int stringp(mixed arg);
When this function is called inside a conditional, the language server narrows the tested variable to the predicate type within the true branch:
void test() {
mixed o;
if(stringp(o)) {
// o is narrowed to string here
string s = o; // OK
}
}
The type predicate does not change the function's actual return type.
The function still returns its declared type (e.g., int); the
predicate only informs the language server's flow analysis.
Type predicates work with any valid type, including primitives, composites, and object file paths:
/**
* @param {mixed} arg
* @returns {arg is mixed*}
*/
int pointerp(mixed arg);
/**
* @param {mixed} o
* @returns {o is "/std/living.lpc"}
*/
int is_living(mixed o);
When to use type predicates: If a function validates the type of
a parameter and returns a truthy or falsy result, it should use a type
predicate. This includes simple type-checking wrappers (like
valid_function), but is not limited to them — any function whose
return value implies a type guarantee about a parameter is a
candidate. A description may follow the predicate to document the
return value for human readers:
/**
* Returns the original object if it is a user, otherwise 0.
*
* @param {object} ob - Some object.
* @returns {ob is "/std/user.lpc"} The original object if it is a
* user, or 0.
*/
object get_user(object ob) {
return ob->is_user() ? ob : 0;
}
Preprocessor defines are resolved in type predicates, so you can use macros as the target type:
#define STD_USER "/std/user.lpc"
/**
* @param {object} ob - Some object.
* @returns {ob is STD_USER} 1 if ob is a user object.
*/
int is_user(object ob);
@throwsDocuments conditions that cause a throw(). A throw() is a soft
error — it can be intercepted by catch() and does not generate a
stack trace. This is the mechanism for recoverable exceptions.
Syntax: @throws Description of the condition
/**
* @throws If the configuration file was not found.
*/
Multiple @throws tags can be used when a function has several throw
conditions.
@errorsDocuments conditions that trigger a hard error — error() in FluffOS
or raise_error() in LDMud. Unlike throw(), a hard error generates
a full stack trace and is expensive. LPC distinguishes between soft
errors (throw()) and hard errors, and @errors exists to document
that distinction.
Syntax: @errors Description of the condition
/**
* @errors If the crafter lacks required skills.
* @errors If components are missing or of insufficient quality.
*/
This will be evident by the appearance of the error() function
within the function body. Also applies to assert() and
assert_arg().
@typeDocuments the type of a variable or expression.
Syntax: @type {type}
/**
* @type {int} Maximum health points for a player.
*/
int MAX_PLAYER_HP = 1000;
/**
* @type {([ string: int ])} Mapping of damage types to resistance
* values.
*/
mapping resistances = ([ "fire": 10, "cold": 5, "physical": 3 ]);
You can annotate an expression inline to assert its type.
object p = /** @type {"/std/player.lpc"} */(get_player());
The @type tag can also be used inline in a function signature to
narrow an object parameter for the LSP. Place it as a comment
immediately before the parameter:
mixed main(/** @type {STD_PLAYER} */ object caller, string str) {
This lets the LSP resolve call_other methods (e.g.,
caller->set_env(...)) that exist on the typed object. Pick the
macro by what the object is known to be, per Named Objects
below — not by the methods this function happens to call. See the
lpc-coding-style skill for full guidance.
@varDocuments the type of an inherited variable. Use this when a variable is declared in a parent object and you want to provide type information in the inheriting file.
Syntax: @var {type} Description
/**
* @var {([ string: int ])} Inherited mapping of skill names to
* levels.
*/
@typedefDefines a named type alias or a structured shape. This is useful for
documenting complex data structures — like the expected shape of a
mapping — without needing a class or struct definition. The
language server resolves object paths in @typedef tags and provides
IntelliSense for the defined type.
Syntax: @typedef {type} Name or @typedef Name followed by
@property tags
/**
* @typedef {int | string} Identifier
*/
Use @property tags to define the members of the type. This is the
"shape definer" — it lets you describe what keys a mapping or data
structure is expected to have, along with their types.
/**
* @typedef PlayerData
* @property {string} name - The player's display name.
* @property {int} level - Current experience level.
* @property {"/std/guild.lpc"} guild - The player's guild object.
* @property {int} hp - Current hit points.
* @property {int} max_hp - Maximum hit points.
*/
You can then use the typedef name in other annotations:
/**
* @param {string} player_name - The name to look up.
* @returns {PlayerData} The player's data record.
*/
mapping get_player_data(string player_name) {
// Implementation
}
Object paths used within @typedef are resolved by the language
server, giving you full IntelliSense when referencing those types:
/**
* @typedef PartyMember
* @property {"/std/player.lpc"} player - The player object.
* @property {string} role - Role in the party (tank, healer, etc.).
* @property {int} joined - Timestamp when they joined.
*/
@callbackDocuments a function that is passed as an argument to another function. Use this to describe the expected signature of callback parameters.
Syntax: @callback name
/**
* @callback sort_func
* @param {mixed} a - The first element to compare.
* @param {mixed} b - The second element to compare.
* @returns {int} Negative, zero, or positive comparison result.
*/
@propertyDocuments a property of a class or struct. Used in the doc comment immediately above the class/struct definition.
Syntax: @property {type} name - Description
/**
* Represents an item available for purchase.
*
* @property {string} short - Display name shown in shop menus.
* @property {string} file - Full path to the item's source file.
* @property {int} cost - Purchase price.
* @property {int} stock - Current quantity available.
*/
class ShopItem {
string short;
string file;
int cost;
int stock;
}
@overloadDocuments multiple calling signatures for a single varargs function
that accepts mixed *args.... Each @overload block describes one
valid way to call the function, with its own @param and @returns
tags.
Place the description before the first @overload. Each @overload
starts a new signature — the @param and @returns tags that follow
it belong to that overload until the next @overload or the end of
the comment.
/**
* Sends a direct message to the specified object.
*
* @overload
* @param {string} str - The message string (sent to
* previous_object()).
*
* @overload
* @param {object} ob - The target object.
* @param {string} str - The message string.
* @param {int} [msg_type] - The message type.
*
* @errors If insufficient arguments are provided.
*/
varargs void tell(mixed *args...) {
// Implementation
}
Tags that apply to all overloads (such as @errors, @throws, or
@example) should be placed after the last @overload block.
@exampleProvides an example code snippet demonstrating usage.
Syntax: @example followed by code on subsequent lines
/**
* Transfers items between two containers.
*
* @example
* int moved = transfer_items(player, chest, "gold_coin", 100);
* if(moved < 100) {
* write("Could only move " + moved + " coins.");
* }
*/
@deprecatedMarks a function, variable, or other element as deprecated. Include a description of what to use instead.
Syntax: @deprecated Description or replacement
/**
* @deprecated Use query_experience() instead.
*/
int get_exp(string player_name) {
return find_player(player_name)->query_experience();
}
@fileProvides file-level documentation. Placed at the top of a file to describe its purpose.
Syntax: @file path/to/file.lpc
/**
* @file /d/area/monsters/dragon.lpc
*
* Implements the elder dragon NPC with fire-breath attacks
* and treasure hoarding behaviour.
*/
@seeCreates a reference to another function, file, or resource.
Syntax: @see reference
/**
* @see check_crafting_skills
* @see /std/container.lpc
*/
@overrideIndicates that a function overrides an inherited definition.
/**
* @override
* @param {string} msg - The message to receive.
*/
void receive_message(string msg) {
// Custom implementation
}
@inheritdocIndicates that a function's documentation should be inherited from the parent definition. When the language server encounters this tag, it pulls the description, parameters, and return documentation from the inherited function.
This is particularly useful in LPC where inherit is common —
rather than duplicating documentation across overrides, you can
inherit it and only document what changes.
/**
* @inheritdoc
*/
void create() {
::create();
// Additional setup
}
You can also add to the inherited documentation. Your description and any additional tags are merged with the parent's:
/**
* @inheritdoc
* Also initialises the combat subsystem.
*/
void create() {
::create();
init_combat();
}
@authorIdentifies the author of the code.
Syntax: @author name
@versionSpecifies the version of the code.
Syntax: @version version
@sinceIndicates when a feature was introduced.
Syntax: @since version or date
@private / @protected / @publicDocuments the visibility of a function or variable. These tags are useful when the visibility cannot be inferred from the code or when you want to be explicit.
/**
* @private
* @param {string} name - The name to validate.
* @returns {int} 1 if valid, 0 if invalid.
*/
static int is_valid_name(string name) {
// Implementation
}
@linkCreates an inline link to another element. Used within descriptions,
wrapped in {@link ...}.
Syntax: {@link reference}
/**
* This method works like {@link other_function} but has improved
* performance.
*/
int - Integerstring - Text stringfloat - Floating-point numberobject - Generic objectmapping - Key-value structurefunction - Function referencebuffer - Binary datamixed - Any typevoid - No return valueclass/struct - Structured data typesFor objects of specific types, use the closest matching STD_* macro rather
than a raw file path:
{STD_MACRO}
If no macro exists, fall back to a full path:
{"/path/to/object.lpc"}
Choosing the right macro: Go shallowest first — pick the shallowest
type the object is known to be, the one you can get the most out of. Shallow
and deep are used here in the inherit-traversal sense, as in
deep_inherit_list(): parents are deeper, so STD_PLAYER is shallower than
STD_BODY, which is shallower than STD_OBJECT. Descending toward
STD_OBJECT only hides members you are entitled to call.
Reason from where the object comes from, not from which methods this function calls today:
caller/tp, or from this_body() on a command
path, and the command lives under cmds/adm/ or cmds/dev/ — no NPC walks
that path, so it is a player: {STD_PLAYER}.{STD_BODY}.{STD_CONTAINER}, or
{STD_OBJECT} if even that is more than the facts support.Descend only as far as the uncertainty actually forces, and fall back to bare
{object} only when the type is genuinely unknowable at that point.
In short: the type should reflect what the object provably is, not the minimum interface the function currently touches. Having to move the annotation shallower later, because a new call needs it, is churn it should have avoided.
For class or struct instances, prefix with class or struct:
{class ShopItem}
For arrays of class instances:
{class ShopItem*}
For arrays of a specific type:
{type*}
Example: {string*} for string array
Typed arrays work with any type — primitives, named objects, classes, and other composites:
{("/std/player.lpc")*} // array of named objects
{([ string: int ])*} // array of mappings
{class ShopItem*} // array of class instances
For mappings with specific key/value types:
{([ keytype: valuetype ])}
Example: {([ string: int ])} for string->int mapping
For values that could be multiple types:
{type1 | type2}
Example: {int | string} for int or string
For function references with signature:
{function(paramtype1, paramtype2): returntype}
Example: {function(int, int): int}
For optional parameters:
@param {type} [name] - Description
With default values:
@param {type} [name=default] - Description
Use undefined to distinguish between a legitimate 0 return and a
"not found" result. Combine it with the pipe union operator for
multiple possible return types:
@returns {int | undefined} The score, or undefined if not found.
*)For cases where any type is acceptable and you want to document this
explicitly, distinct from mixed:
@param {*} value - Any value to be stored.
Tuples are represented as arrays with the member types within:
({ string, int, float })
Complex data structures can use nested type annotations:
{([ string: ([ string: int ]) ])}
/**
* Transfers items between two containers.
*
* This function handles weight limits and ownership restrictions.
*
* @param {"/std/container.lpc"} source - The source container
* @param {"/std/container.lpc"} target - The target container
* @param {string} itemId - The identifier of the item to transfer
* @param {int} [count=1] - The number of items to transfer
* @returns {int} The number of items successfully transferred
* @throws If either container does not exist
* @errors If the item cannot be found in the source
* @errors If the target is full or over weight limit
* @example
* int moved = transferItems(player, chest, "gold_coin", 100);
* if(moved < 100) {
* write("Could only move " + moved + " coins.");
* }
*/
Classes should be documented to describe their properties:
/**
* Represents a parsed GMCP message with its components.
*
* @property {string} name - Full message string
* @property {string} package - First component of the message
* @property {string} module - Second component of the message
* @property {string} submodule - Third component, if present
* @property {mixed} payload - Decoded payload data
*/
class ClassGMCP {
// Implementation
}
Key points for class documentation:
Unless specifically instructed, do not document:
mixed main() in commandsvoid setup()void mudlib_setup()void base_setup()void pre_setup_0() through void pre_setup_4()void post_setup_0() through void post_setup_4()File-global variables only require documentation when the @type
annotation gives the LSP shape information it cannot already infer
from the declaration. Document globals that are objects (STD_*
macros or file paths), structured mappings, classes, or arrays of
those — anywhere the type narrowing helps reference sites. Plain
primitives (int, string, float) and untyped containers do not
need a doc block; the declaration already tells the LSP everything
it needs.
File headers should use this format:
/**
* @file /std/living/boon.lpc
*
* Buffs/debuffs and other boons for living objects.
*
* @created 2024-07-30 - Gesslar
* @last_modified 2024-07-30 - Gesslar
*
* @history
* 2024-07-30 - Gesslar - Created
*/
Structural rules — each of these blank lines is required:
@file and the description.@created block.@last_modified and @history.When encountering file headers that do not follow this format, update them. Preserve the existing information (author, dates, history) but restructure into the standard format. Clarify any vague or unclear descriptions.
Otherwise, if unsure, ask.
The following tags are not supported and should be removed on sight when encountered in existing documentation. Do not introduce them in new documentation.
@description — Remove the tag and keep the text as the leading
description paragraph (the description is always the first thing in
the comment block; it does not need a tag).The LPC Language Services extension supports comment directives that suppress or control diagnostic output. Unlike LPCDoc tags (which provide documentation and type information), these are standalone single-line comments that control the type checker's behaviour.
@lpc-ignoreSuppresses all diagnostics on the immediately following line.
// @lpc-ignore - ignore int to string assignment error
string foo = 123;
@lpc-nocheckDisables all diagnostics for the entire file. Must be placed at the top of the file.
// @lpc-nocheck
@lpc-expect-errorAsserts that the next line produces a diagnostic. If the expected error does not occur, the directive itself becomes an error. Useful for intentional type violations.
// @lpc-expect-error: method does not exist
o->foo();
Do not touch the code. For documentation purposes, we only need comments. There will be no reason to opine on things like parens placement, semicolon changes, etc., by updating the code sections of a file. Restrict activities to only documentation.