Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
99 changes: 99 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

GCS (GURPS Character Sheet) is a stand-alone, interactive desktop GUI application for building characters for the
GURPS Fourth Edition tabletop RPG. It is written in Go and uses [Unison](https://github.com/richardwilkes/unison)
(the author's own GLFW/OpenGL-based toolkit) for the UI and OS integration. Module path: `github.com/richardwilkes/gcs/v5`.

## Critical: GOEXPERIMENT=jsonv2

The code imports `encoding/json/v2` and `encoding/json/jsontext` (~66 files). **Every Go invocation must run with
`GOEXPERIMENT=jsonv2` in the environment** or it will not compile. `build.sh` exports it and `.vscode/settings.json`
sets it for the Go tooling, but any manual command must set it explicitly:

```bash
GOEXPERIMENT=jsonv2 go test ./model/gurps/ -run TestName
GOEXPERIMENT=jsonv2 go build ./...
GOEXPERIMENT=jsonv2 go vet ./...
```

## Common commands

All builds go through `./build.sh` (run `./build.sh -h` for the full list):

| Command | Purpose |
| --- | --- |
| `./build.sh` | Default: generate source, then build the Go binary |
| `./build.sh -G` | Regenerate generated source (enums) |
| `./build.sh -t` | Run all tests |
| `./build.sh -r` | Run tests with the race detector |
| `./build.sh -l` | Run golangci-lint (auto-installs the matching version into `$GOPATH/bin`) |
| `./build.sh -a` | Everything: gen + build + lint + race tests + package |
| `./build.sh -d` | Build a distribution (sets the release version) |
| `./build.sh -i` | Extract the i18n translation template |
| `./build.sh -p` | Regenerate icons and `packaging.yml` |

Run a single test directly: `GOEXPERIMENT=jsonv2 go test ./model/fxp/ -run TestWeight`.

Linting uses **golangci-lint v2** (config in `.golangci.yml`) with a broad linter set (gocritic all-tags, gosec,
revive, staticcheck, govet enable-all). Formatting is enforced by **gofumpt (extra-rules) + goimports** — plain
`gofmt` output will not pass.

## Architecture

The codebase has a strict **model / UI split**:

- **`model/`** — all domain logic and business rules, no UI dependencies.
- **`model/gurps/`** — the heart of the application (~140 files). `Entity` (the character) is the root aggregate;
its children are the `Node[T]` types: `Trait`, `TraitModifier`, `Skill`, `Spell`, `Equipment`,
`EquipmentModifier`, `Note`, `Weapon`, `ConditionalModifier`. `node.go` defines the generic `Node[T NodeTypes]`
interface these all implement — these are the tree/table rows shown in the UI. Features, prerequisites, and
bonuses are also modeled here.
- **`model/fxp/`** — fixed-point decimal math (`fxp.Int`, backed by toolbox `fixed64`), plus `Length` and `Weight`
with units. **All game numbers use `fxp.Int`, never `float64`.**
- **`model/jio/`** — JSON load/save and data versioning. `CurrentDataVersion = 5`, `MinimumDataVersion = 2`; all GCS
data files share one version number.
- Other support packages: `criteria`, `nameable`, `colors`, `fonts`, `paper`, `kinds`.
- **`ux/`** — the UI layer (~150 files) built on Unison. Heavily depends on `model/gurps`. The workspace is a
dock-based UI of `*Dockable` panels (character sheets, templates, settings, PDF/markdown viewers). `ux.Start()` is
the GUI entry point and never returns.
- **`main.go`** — entry point. Configures app identity via `early.Configure()`, then either runs a headless CLI mode
(`--convert`, `--sync`, `--text` export) or launches the GUI through `ux.Start()`.
- **`cmd/`** — auxiliary tools, not part of the app binary: `enumgen` (enum code generation), `genpkg` (icons +
packaging), `pack` (distribution packaging), `prereq-counts`.

## Code generation (enums)

Enums are code-generated. The canonical list of all enums lives in the `allEnums` variable in
[cmd/enumgen/main.go](cmd/enumgen/main.go). For each enum there is a hand-written file (e.g.
`model/gurps/enums/difficulty/level.go`, holding custom methods) and a generated companion
`*_gen.go` file (constants, `String()`, marshaling — marked `// Code generated ... DO NOT EDIT`). Most enums live in
their own package under `model/gurps/enums/<name>/`.

To add or change an enum: edit `allEnums` in `cmd/enumgen/main.go` (and the hand-written method file if needed), then
run `./build.sh -G`. **Regeneration deletes every `*_gen.go` file in the repo and rewrites it**, so never hand-edit a
`_gen.go` file.

## Libraries & data files

Character/library content lives in **libraries** — Git repositories (a master library, the user library, and add-on
libraries) that can be browsed and updated from within the app via go-git ([library.go](model/gurps/library.go),
[git_latest.go](model/gurps/git_latest.go)). Individual data nodes can be linked to a library "source" and synced
(`GetSource`/`SyncWithSource`, `srcstate` enum). File extensions (`.gcs` sheet, `.gct` template, `.adq` traits, `.skl`
skills, `.spl` spells, `.eqp` equipment, etc.) are defined in [file_type.go](model/gurps/file_type.go).

## Scripting

Calculated values can be driven by embedded JavaScript via [goja](https://github.com/dop251/goja). The bridge lives
in `model/gurps/scripting*.go`, which exposes the entity, attributes, skills, spells, traits, equipment, weapons, and
dice to scripts.

## Conventions

- Every Go source file begins with the MPL 2.0 copyright header (see any existing `.go` file); new files must include it.
- User-facing strings are wrapped in `i18n.Text(...)`; `./build.sh -i` extracts the translation template.
- The toolbox v2 library (`xos`, `errs`, `i18n`, `tid`, `fixed64`, …) is used pervasively for cross-platform and
utility needs — prefer it over rolling your own.
8 changes: 6 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
"name": "GCS",
"type": "go",
"request": "launch",
"mode": "auto",
"program": ".",
"mode": "exec",
"preLaunchTask": "build GCS",
"program": "${workspaceFolder}/gcs",
"windows": {
"program": "${workspaceFolder}/gcs.exe"
},
"args": [
"-console"
]
Expand Down
23 changes: 23 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"version": "2.0.0",
"tasks": [
{
// Build GCS so the debugger can attach to a binary that launches and renders correctly. Paths are relative
// (with cwd set below) so they survive whatever shell the task runs in.
//
// Windows: force Go's internal linker (-linkmode=internal). With the default external mingw linker, large
// unstripped (debug-info) binaries are rejected by the Windows loader with error 193 ("not a valid Win32
// application") -- see golang/go#75077. Stripping (-s -w) also avoids it but would discard the DWARF info
// dlv needs. Internal linking keeps full debug info AND produces a loadable binary. macOS/Linux don't hit
// this bug (and darwin cgo can't be internally linked), so they use a plain build.
"label": "build GCS",
"type": "shell",
"command": "./build.sh",
"options": {
"cwd": "${workspaceFolder}"
},
"group": "build",
"problemMatcher": ["$go"]
}
]
}
14 changes: 7 additions & 7 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ set -eo pipefail

trap 'echo -e "\033[33;5mBuild failed on build.sh:$LINENO\033[0m"' ERR

export GOEXPERIMENT=jsonv2
export GOEXPERIMENT=jsonv2,nodwarf5

# Process args
RELEASE="0.0"
EXTRA_LD_FLAGS="-s -w"
for arg in "$@"; do
case "$arg" in
--all | -a)
Expand Down Expand Up @@ -50,6 +49,7 @@ for arg in "$@"; do
SOMETHING=1
;;
--dist | -d)
EXTRA_LD_FLAGS="-s -w"
EXTRA_BUILD_FLAGS="-a -trimpath"
RELEASE="5.42.0"
PACKAGER=1
Expand Down Expand Up @@ -88,9 +88,6 @@ if [ "$SOMETHING"x != "1x" ]; then
BUILD_GO=1
fi

LDFLAGS_ALL="-X github.com/richardwilkes/toolbox/v2/xos.AppVersion=$RELEASE $EXTRA_LD_FLAGS"
STD_FLAGS="-v -buildvcs=true $EXTRA_BUILD_FLAGS"

case $(uname -s) in
Darwin*)
if [ "$(uname -p)" == "arm" ]; then
Expand All @@ -99,11 +96,14 @@ Darwin*)
export MACOSX_DEPLOYMENT_TARGET=10.15
fi
;;
MINGW*)
LDFLAGS_ALL="$LDFLAGS_ALL -H windowsgui"
MINGW*|MSYS*)
EXTRA_LD_FLAGS="$EXTRA_LD_FLAGS -H windowsgui"
;;
esac

LDFLAGS_ALL="-X github.com/richardwilkes/toolbox/v2/xos.AppVersion=$RELEASE $EXTRA_LD_FLAGS"
STD_FLAGS="-v -buildvcs=true $EXTRA_BUILD_FLAGS"

# Generate the source
if [ "$BUILD_GEN"x == "1x" ]; then
echo -e "\033[33mGenerating...\033[0m"
Expand Down
2 changes: 1 addition & 1 deletion cmd/enumgen/enum.go.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 1998-2025 by Richard A. Wilkes. All rights reserved.
// Copyright (c) 1998-2026 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
Expand Down
2 changes: 1 addition & 1 deletion cmd/enumgen/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 1998-2025 by Richard A. Wilkes. All rights reserved.
// Copyright (c) 1998-2026 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
Expand Down
23 changes: 16 additions & 7 deletions cmd/genpkg/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 1998-2025 by Richard A. Wilkes. All rights reserved.
// Copyright (c) 1998-2026 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
Expand All @@ -24,6 +24,7 @@ import (
"github.com/richardwilkes/gcs/v5/svg"
"github.com/richardwilkes/gcs/v5/ux"
"github.com/richardwilkes/toolbox/v2/errs"
"github.com/richardwilkes/toolbox/v2/uti"
"github.com/richardwilkes/toolbox/v2/ximage"
"github.com/richardwilkes/toolbox/v2/xos"
"github.com/richardwilkes/toolbox/v2/xyaml"
Expand Down Expand Up @@ -71,19 +72,19 @@ func main() {
if one.IsSpecial {
continue
}
extensions := make([]string, len(one.Extensions))
for i, ext := range one.Extensions {
extensions := make([]string, len(one.UTI.Extensions))
for i, ext := range one.UTI.Extensions {
extensions[i] = ext[1:]
}
data := packager.FileData{
Name: one.Name,
Icon: "pkgicons/" + extensions[0] + "_doc.png",
Role: "Viewer",
Rank: "Alternate",
UTI: one.UTI,
ConformsTo: one.ConformsTo,
UTI: one.UTI.UTI,
ConformsTo: extractConformsTo(one.UTI),
Extensions: extensions,
MimeTypes: one.MimeTypes,
MimeTypes: one.UTI.MimeTypes,
}
if one.IsGCSData {
data.Role = "Editor"
Expand Down Expand Up @@ -113,7 +114,7 @@ func main() {
overlay, err = svg.CreateImageFromSVG(fi.SVG, 512)
xos.ExitIfErr(err)
var f *os.File
f, err = os.Create(filepath.Join(iconsPath, fi.Extensions[0][1:]+"_doc.png"))
f, err = os.Create(filepath.Join(iconsPath, fi.UTI.Extensions[0][1:]+"_doc.png"))
xos.ExitIfErr(err)
xos.ExitIfErr(errs.Wrap(png.Encode(f, ximage.Stack(docImg, overlay))))
xos.ExitIfErr(f.Close())
Expand All @@ -123,3 +124,11 @@ func main() {
}),
)
}

func extractConformsTo(u *uti.DataType) []string {
list := make([]string, len(u.Parents))
for i, p := range u.Parents {
list[i] = p.UTI
}
return list
}
2 changes: 1 addition & 1 deletion cmd/pack/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 1998-2025 by Richard A. Wilkes. All rights reserved.
// Copyright (c) 1998-2026 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
Expand Down
2 changes: 1 addition & 1 deletion cmd/prereq-counts/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 1998-2025 by Richard A. Wilkes. All rights reserved.
// Copyright (c) 1998-2026 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
Expand Down
2 changes: 1 addition & 1 deletion debug/profile.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 1998-2025 by Richard A. Wilkes. All rights reserved.
// Copyright (c) 1998-2026 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
Expand Down
2 changes: 1 addition & 1 deletion early/early.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 1998-2025 by Richard A. Wilkes. All rights reserved.
// Copyright (c) 1998-2026 by Richard A. Wilkes. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with
Expand Down
56 changes: 26 additions & 30 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,54 +1,50 @@
module github.com/richardwilkes/gcs/v5

go 1.25.3
go 1.26.0

require (
github.com/dop251/goja v0.0.0-20251103141225-af2ceb9156d7
github.com/go-git/go-billy/v6 v6.0.0-20251103194943-ae9e5d5b5b37
github.com/go-git/go-git/v6 v6.0.0-20251103200709-47b1ed2930c9
github.com/dop251/goja v0.0.0-20260607120635-348e6bea910d
github.com/go-git/go-billy/v6 v6.0.0-alpha.1
github.com/go-git/go-git/v6 v6.0.0-alpha.4
github.com/google/uuid v1.6.0
github.com/richardwilkes/pdf v1.25.4
github.com/richardwilkes/rpgtools v1.11.0
github.com/richardwilkes/toolbox/v2 v2.8.0
github.com/richardwilkes/unison v0.90.5
github.com/richardwilkes/pdf v1.26.12
github.com/richardwilkes/rpgtools v1.12.0
github.com/richardwilkes/toolbox/v2 v2.13.2
github.com/richardwilkes/unison v0.90.6-0.20260614213213-84a4ca546693
github.com/rjeczalik/notify v0.9.3
github.com/yookoala/realpath v1.0.0
github.com/zeebo/xxh3 v1.0.2
golang.org/x/image v0.32.0
golang.org/x/sys v0.37.0
golang.org/x/text v0.30.0
github.com/zeebo/xxh3 v1.1.0
golang.org/x/image v0.42.0
golang.org/x/sys v0.46.0
golang.org/x/text v0.38.0
)

require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/OpenPrinting/goipp v1.2.0 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/ProtonMail/go-crypto v1.4.1 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/dlclark/regexp2/v2 v2.2.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg/v2 v2.0.2 // indirect
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d // indirect
github.com/google/pprof v0.0.0-20260604005048-7023385849c0 // indirect
github.com/grandcat/zeroconf v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/miekg/dns v1.1.68 // indirect
github.com/miekg/dns v1.1.72 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/tc-hib/winres v0.3.1 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/tools v0.38.0 // indirect
github.com/yuin/goldmark v1.8.2 // indirect
golang.org/x/crypto v0.53.0 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/tools v0.46.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading