aboutsummaryrefslogtreecommitdiff
path: root/eslint.config.js
blob: d01013166628ecc367fdc7e2c1a60d6c442778e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import js from '@eslint/js'
import ts from 'typescript-eslint'
import svelte from 'eslint-plugin-svelte'
import prettier from 'eslint-config-prettier'
import globals from 'globals'

const gameplayDirectories = [
  'engine/**/*.{ts,js,svelte}',
  'events/**/*.{ts,js,svelte}',
  'content-system/**/*.{ts,js,svelte}',
  'flow/**/*.{ts,js,svelte}',
  'scene/**/*.{ts,js,svelte}',
  'memoir/**/*.{ts,js,svelte}',
  'birth/**/*.{ts,js,svelte}',
  'death/**/*.{ts,js,svelte}',
  'continuation/**/*.{ts,js,svelte}',
  'worldgen/**/*.{ts,js,svelte}',
  'persistence/**/*.{ts,js,svelte}',
  'rng/**/*.{ts,js,svelte}',
  'time/**/*.{ts,js,svelte}',
  'loading/**/*.{ts,js,svelte}',
  'ui-lib/**/*.{ts,js,svelte}',
  'routes/**/*.{ts,js,svelte}',
  'utils/**/*.{ts,js,svelte}'
]

export default ts.config(
  {
    ignores: [
      '.svelte-kit/**',
      'build/**',
      'dist/**',
      'built/**',
      'node_modules/**',
      '.claude/**',
      'coverage/**'
    ]
  },
  js.configs.recommended,
  ...ts.configs.recommended,
  ...svelte.configs['flat/recommended'],
  prettier,
  ...svelte.configs['flat/prettier'],
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node
      }
    }
  },
  {
    files: ['**/*.svelte', '**/*.svelte.ts'],
    languageOptions: {
      parserOptions: {
        projectService: true,
        extraFileExtensions: ['.svelte']
      }
    }
  },
  {
    files: gameplayDirectories,
    rules: {
      'no-restricted-syntax': [
        'error',
        {
          selector:
            "CallExpression[callee.type='MemberExpression'][callee.object.name='Math'][callee.property.name='random']",
          message:
            'Math.random() is forbidden in gameplay code. Use the seeded RNG from rng/ — determinism is load-bearing (ARCHITECTURE.md §26).'
        },
        {
          selector:
            "CallExpression[callee.type='MemberExpression'][callee.object.name='crypto'][callee.property.name='getRandomValues']",
          message:
            'crypto.getRandomValues() is forbidden in gameplay code. Use the seeded RNG from rng/.'
        },
        {
          selector:
            "CallExpression[callee.type='MemberExpression'][callee.object.name='Date'][callee.property.name='now']",
          message:
            'Date.now() is forbidden in gameplay code. Use GameTime from time/ for gameplay logic. Date.now() is permitted only in tests, scripts, and outside the simulation (metadata, logging, performance measurement).'
        }
      ],
      '@typescript-eslint/no-explicit-any': 'error'
    }
  },
  {
    files: ['tests/**', 'scripts/**', '**/*.{test,spec}.ts', '**/*.test.svelte'],
    rules: {
      'no-restricted-syntax': 'off',
      '@typescript-eslint/no-explicit-any': 'off'
    }
  }
)