getreadystack commited on
Commit
e6aad1b
·
verified ·
1 Parent(s): f39affc

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +356 -17
index.html CHANGED
@@ -1,19 +1,358 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Ren'Py Release Gate — free online check</title>
7
+ <style>
8
+ :root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
9
+ .wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
10
+ textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
11
+ .bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
12
+ input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
13
+ table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
14
+ .sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
15
+ .note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
16
+ </style>
17
+ <script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
18
+ <!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/renpy-release-gate"></head>
19
+ <body>
20
+ <div class="wrap">
21
+ <h1>Ren'Py Release Gate</h1>
22
+ <p class="lede">Catch the Ren'Py script bugs that compile fine and only crash in a player's save: unescaped [ and {, define-instead-of-default, tab indents, developer mode shipped. Runs entirely in your browser — nothing is uploaded.</p>
23
+ <textarea id="src" spellcheck="false"># dirty.rpy - compiles, runs on your machine, breaks in a player's save.
24
+
25
+ define config.name = _("Harbour Lights")
26
+ define config.version = "1.0"
27
+ define config.developer = True
28
+ define config.console = True
29
+ define config.autoreload = True
30
+ define config.image_cache_size = 400
31
+
32
+ define affection = 0
33
+
34
+ init python:
35
+ build.name = "HarbourLights"
36
+ build.classify('game/**.txt', None)
37
+
38
+ image bg harbour = im.Scale("images/bg/harbour.png", 1920, 1080)
39
+ image logo = "C:/Users/dev/art/logo.png"
40
+
41
+ label start:
42
+ scene bg harbour with fade
43
+ play music "audio/theme.ogg" fadein 1.0
44
+ "Welcome back, [ player_name]."
45
+ "{i}Late again, she thought."
46
+ $ affection += 1
47
+ if persistent.seen_ending_a:
48
+ "You have stood on this pier before."
49
+ "The tab on this line is not indentation Ren'Py accepts."
50
+ return</textarea>
51
+ <div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
52
+ <div id="out" aria-live="polite"></div>
53
+ <div id="paid"><strong>Free here:</strong> Scan one .rpy file and name every line that ships broken, with the fix for each.. <strong>In the editor:</strong> Gate the whole project at once - every .rpy file in the workspace - and export a release report you keep. — that part asks for a licence key.</div>
54
+ <p class="note">Same engine as the VS Code extension, byte for byte.</p>
55
+ </div>
56
+ <script>var RENPY_RULES = [
57
+ {
58
+ "id": "unescaped_substitution",
59
+ "sev": "error",
60
+ "title": "Square bracket in dialogue is read as a variable",
61
+ "fix": "Escape it as \\[ , or close the interpolation with a real variable name."
62
+ },
63
+ {
64
+ "id": "unbalanced_text_tag",
65
+ "sev": "error",
66
+ "title": "Text tag opened and never closed",
67
+ "fix": "Close it, for example {i}...{/i}, or escape a literal brace as \\{."
68
+ },
69
+ {
70
+ "id": "define_not_default",
71
+ "sev": "error",
72
+ "title": "define used for a variable that changes during play",
73
+ "fix": "Use default instead of define so the value is saved and rolled back."
74
+ },
75
+ {
76
+ "id": "persistent_no_default",
77
+ "sev": "error",
78
+ "title": "persistent value read with no default",
79
+ "fix": "Add default persistent.NAME = ... so a first-time player does not read None."
80
+ },
81
+ {
82
+ "id": "tab_indent",
83
+ "sev": "error",
84
+ "title": "Tab used for indentation",
85
+ "fix": "Ren'Py indentation must be spaces. Replace the tab with spaces."
86
+ },
87
+ {
88
+ "id": "developer_mode_shipped",
89
+ "sev": "error",
90
+ "title": "config.developer is hard-set to True",
91
+ "fix": "Set config.developer = \"auto\" so the console and dev menu stay out of the build."
92
+ },
93
+ {
94
+ "id": "console_enabled",
95
+ "sev": "error",
96
+ "title": "config.console is left True",
97
+ "fix": "Remove the line or set it False before you build the distribution."
98
+ },
99
+ {
100
+ "id": "autoreload_enabled",
101
+ "sev": "warn",
102
+ "title": "config.autoreload is left True",
103
+ "fix": "Leave autoreload to developer mode; do not pin it True in a shipped build."
104
+ },
105
+ {
106
+ "id": "missing_save_directory",
107
+ "sev": "error",
108
+ "title": "config.save_directory is not set",
109
+ "fix": "Define config.save_directory once and never change it, so saves survive a rename."
110
+ },
111
+ {
112
+ "id": "placeholder_version",
113
+ "sev": "warn",
114
+ "title": "config.version is still the template placeholder",
115
+ "fix": "Set the real version; storefronts and crash reports read this string."
116
+ },
117
+ {
118
+ "id": "source_rpy_in_build",
119
+ "sev": "warn",
120
+ "title": "Build rules do not exclude .rpy source",
121
+ "fix": "Add build.classify('game/**.rpy', None) if you do not want readable script source in the distribution."
122
+ },
123
+ {
124
+ "id": "deprecated_im_api",
125
+ "sev": "warn",
126
+ "title": "Deprecated im.* image manipulator",
127
+ "fix": "Replace with a Transform or a Composite displayable."
128
+ },
129
+ {
130
+ "id": "removed_image_cache_size",
131
+ "sev": "error",
132
+ "title": "config.image_cache_size is the removed setting",
133
+ "fix": "Use config.image_cache_size_mb, which is measured in megabytes."
134
+ },
135
+ {
136
+ "id": "nonportable_asset_path",
137
+ "sev": "error",
138
+ "title": "Asset path will not resolve outside your machine",
139
+ "fix": "Use a relative, forward-slash path inside game/, e.g. \"images/bg/harbour.webp\"."
140
+ }
141
+ ];</script>
142
+ <script>// Ren'Py Release Gate - one brain, used by the VS Code extension and by the free web page.
143
+ (function (root) {
144
+ 'use strict';
145
+
146
+ var RULES = (typeof module !== 'undefined' && module.exports)
147
+ ? require('./rules.json')
148
+ : root.RENPY_RULES;
149
+
150
+ var BY_ID = {};
151
+ RULES.forEach(function (r) { BY_ID[r.id] = r; });
152
+
153
+ var ASSET_EXT = /\.(png|jpg|jpeg|webp|webm|avi|ogg|opus|mp3|wav|ttf|otf|rpy|json|txt)$/i;
154
+
155
+ // Pull every string literal out of one line, keeping its 1-based column.
156
+ function strings(line) {
157
+ var out = [], i = 0, n = line.length;
158
+ while (i < n) {
159
+ var q = line[i];
160
+ if (q === '"' || q === "'") {
161
+ var j = i + 1, buf = '';
162
+ while (j < n) {
163
+ if (line[j] === '\\') { buf += line[j] + (line[j + 1] || ''); j += 2; continue; }
164
+ if (line[j] === q) break;
165
+ buf += line[j]; j++;
166
+ }
167
+ if (j >= n) break; // unterminated - leave it to Ren'Py
168
+ out.push(buf);
169
+ i = j + 1;
170
+ } else { i++; }
171
+ }
172
+ return out;
173
+ }
174
+
175
+ // Depth of an opener/closer pair, ignoring backslash-escaped ones.
176
+ function balance(s, open, close) {
177
+ var depth = 0, spaced = false;
178
+ for (var i = 0; i < s.length; i++) {
179
+ if (s[i] === '\\') { i++; continue; }
180
+ if (s[i] === open) {
181
+ depth++;
182
+ if (i + 1 >= s.length || s[i + 1] === ' ' || s[i + 1] === close) spaced = true;
183
+ } else if (s[i] === close) { depth--; }
184
+ }
185
+ return { depth: depth, spaced: spaced };
186
+ }
187
+
188
+ // Text tags that must be closed. {w} {p} {nw} {fast} and friends stand alone.
189
+ var PAIRED = ['a','alpha','alt','art','b','color','cps','font','i','k','noalt','outlinecolor','plain','rb','rt','s','size','u'];
190
+
191
+ // Name of the first text tag left open in this string, or '' if the string is sound.
192
+ function openTag(s) {
193
+ var stack = [], m, re = /\{([^{}]*)\}/g, clean = s.replace(/\\./g, '');
194
+ if ((clean.match(/\{/g) || []).length !== (clean.match(/\}/g) || []).length) return 'unclosed brace';
195
+ while ((m = re.exec(clean)) !== null) {
196
+ var body = m[1];
197
+ if (body.charAt(0) === '/') {
198
+ var want = body.slice(1);
199
+ if (stack.length && stack[stack.length - 1] === want) stack.pop();
200
+ else return '/' + want;
201
+ } else {
202
+ var name = body.split('=')[0].trim().toLowerCase();
203
+ if (PAIRED.indexOf(name) !== -1) stack.push(name);
204
+ }
205
+ }
206
+ return stack.length ? stack[stack.length - 1] : '';
207
+ }
208
+
209
+ function check(text, opts) {
210
+ opts = opts || {};
211
+ var src = String(text == null ? '' : text);
212
+ var lines = src.split(/\r?\n/);
213
+ var findings = [];
214
+ var seen = {};
215
+
216
+ function add(id, line, detail) {
217
+ var r = BY_ID[id];
218
+ findings.push({
219
+ check: id,
220
+ sev: r.sev,
221
+ msg: r.title + (detail ? ': ' + detail : '') + '. ' + r.fix,
222
+ line: line
223
+ });
224
+ }
225
+
226
+ var defines = {}; // bare name -> line of its define
227
+ var persistentDefault = {};
228
+ var persistentRead = {};
229
+ var buildLine = 0, hasBuild = false, excludesRpy = false;
230
+ var nameLine = 0, hasSaveDir = false;
231
+ var inInit = false;
232
+
233
+ lines.forEach(function (raw, idx) {
234
+ var ln = idx + 1;
235
+ var line = raw;
236
+ var code = line.replace(/#.*$/, '');
237
+
238
+ // init-block tracking: an init statement at column 0 opens a block.
239
+ if (/^\s*init\b[^:]*:\s*$/.test(code)) { inInit = true; }
240
+ else if (code.trim() && !/^\s/.test(code)) { inInit = false; }
241
+
242
+ if (/^[ ]*\t/.test(line)) add('tab_indent', ln);
243
+
244
+ if (/\bconfig\.developer\s*=\s*True\b/.test(code)) add('developer_mode_shipped', ln);
245
+ if (/\bconfig\.console\s*=\s*True\b/.test(code)) add('console_enabled', ln);
246
+ if (/\bconfig\.autoreload\s*=\s*True\b/.test(code)) add('autoreload_enabled', ln);
247
+ if (/\bconfig\.image_cache_size(?!_mb)\s*=/.test(code)) add('removed_image_cache_size', ln);
248
+ if (/\bconfig\.version\s*=\s*["']1\.0["']/.test(code)) add('placeholder_version', ln);
249
+
250
+ if (/\bconfig\.name\s*=/.test(code) && !nameLine) nameLine = ln;
251
+ if (/\bconfig\.save_directory\s*=/.test(code)) hasSaveDir = true;
252
+
253
+ if (/\bbuild\.[A-Za-z_]/.test(code)) { if (!hasBuild) { hasBuild = true; buildLine = ln; } }
254
+ if (/build\.classify\(\s*["'][^"']*\.rpy["']\s*,\s*None\s*\)/.test(code)) excludesRpy = true;
255
+
256
+ var im = code.match(/\bim\.(Scale|FactorScale|MatrixColor|Sepia|Grayscale|Composite|Crop|Flip|Tile|Alpha)\b/);
257
+ if (im) add('deprecated_im_api', ln, 'im.' + im[1]);
258
+
259
+ var def = code.match(/^\s*define\s+([A-Za-z_][A-Za-z0-9_]*)\s*=/);
260
+ if (def && !defines[def[1]]) defines[def[1]] = ln;
261
+
262
+ var pd = code.match(/^\s*default\s+persistent\.([A-Za-z_][A-Za-z0-9_]*)/);
263
+ if (pd) persistentDefault[pd[1]] = true;
264
+
265
+ var pw = code.match(/\bpersistent\.([A-Za-z_][A-Za-z0-9_]*)\s*=(?!=)/);
266
+ if (pw) { if (inInit) persistentDefault[pw[1]] = true; }
267
+ else {
268
+ var pr = code.match(/\bpersistent\.([A-Za-z_][A-Za-z0-9_]*)/);
269
+ if (pr && !/^\s*default\b/.test(code) && !persistentRead[pr[1]]) persistentRead[pr[1]] = ln;
270
+ }
271
+
272
+ strings(code).forEach(function (s) {
273
+ var sq = balance(s, '[', ']');
274
+ if (sq.depth !== 0 || sq.spaced) add('unescaped_substitution', ln, JSON.stringify(s.slice(0, 48)));
275
+ var tag = openTag(s);
276
+ if (tag) add('unbalanced_text_tag', ln, '{' + tag + '} in ' + JSON.stringify(s.slice(0, 40)));
277
+ if (ASSET_EXT.test(s) && (/\\/.test(s) || /^[A-Za-z]:/.test(s) || s.charAt(0) === '/' || s.indexOf('..') === 0)) {
278
+ add('nonportable_asset_path', ln, JSON.stringify(s.slice(0, 48)));
279
+ }
280
+ });
281
+ });
282
+
283
+ // define used for something that is reassigned later
284
+ Object.keys(defines).forEach(function (name) {
285
+ var re = new RegExp('^\\s*(?:\\$\\s*)?' + name + '\\s*(?:=(?!=)|\\+=|-=|\\*=|/=)');
286
+ for (var i = 0; i < lines.length; i++) {
287
+ if (i + 1 === defines[name]) continue;
288
+ if (re.test(lines[i].replace(/#.*$/, ''))) { add('define_not_default', defines[name], name); return; }
289
+ }
290
+ });
291
+
292
+ Object.keys(persistentRead).forEach(function (name) {
293
+ if (!persistentDefault[name]) add('persistent_no_default', persistentRead[name], 'persistent.' + name);
294
+ });
295
+
296
+ if (nameLine && !hasSaveDir) add('missing_save_directory', nameLine);
297
+ if (hasBuild && !excludesRpy) add('source_rpy_in_build', buildLine);
298
+
299
+ findings.sort(function (a, b) { return (a.line || 0) - (b.line || 0); });
300
+ findings = findings.filter(function (f) {
301
+ var k = f.check + '@' + f.line + '@' + f.msg;
302
+ if (seen[k]) return false; seen[k] = true; return true;
303
+ });
304
+
305
+ return { findings: findings, rule_count: RULES.length, today: opts.today || '' };
306
+ }
307
+
308
+ var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
309
+ if (typeof module !== 'undefined' && module.exports) module.exports = api;
310
+ root.RENPYENGINE = api;
311
+ })(typeof window !== 'undefined' ? window : globalThis);
312
+ </script>
313
+ <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
314
+ 'use strict';
315
+ function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
316
+ function summaryLine(res) {
317
+ const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
318
+ return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
319
+ }
320
+ function toText(res, label) {
321
+ const f = (res && res.findings) || [];
322
+ const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
323
+ f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
324
+ return lines.join('\n');
325
+ }
326
+ function toHtml(res) {
327
+ const f = (res && res.findings) || [];
328
+ if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
329
+ const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
330
+ return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
331
+ }
332
+ var RENPYREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
333
+ if (typeof module !== 'undefined' && module.exports) module.exports = RENPYREPORT;
334
+ else window.RENPYREPORT = RENPYREPORT;
335
+ </script>
336
+ <script>
337
+ (function(){
338
+ var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
339
+ today.value=new Date().toISOString().slice(0,10);
340
+ function run(){ try { var res=window.RENPYENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.RENPYREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
341
+ go.onclick=run; if (src.value.trim()) run();
342
+ })();
343
+ </script>
344
+
345
+ <section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind Ren'Py Release Gate every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"renpy-release-gate",lang:"en",source:"rule_alert"})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script></section>
346
+
347
+
348
+ <section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://open-vsx.org/extension/readystack/renpy-release-gate" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'renpy-release-gate',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/renpy-release-gate" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'renpy-release-gate',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://hub.docker.com/r/getreadystack/renpy-release-gate" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'renpy-release-gate',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">npx @readystack/renpy-release-gate &lt;file&gt;
349
+ docker run --rm -v &quot;$PWD:/w&quot; getreadystack/renpy-release-gate /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://buy.polar.sh/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
350
+ <section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Find the .rpy lines that compile clean and only break inside a player's save</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">Putting one title on Steam costs $100 in Steam Direct fees before a single crash report arrives, and every crash fix after that is another build, another store update and another refund.</p><a href="https://buy.polar.sh/polar_cl_SbAs69NIkhPJABSgWl4Gx0cx54SRPDVWL5mHL3Tg52Q" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
351
+ <section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Ren&#x27;Py Release Gate actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads your .rpy files and reports the lines that pass Ren&#x27;Py&#x27;s compile step but fail later: a bare square bracket that Ren&#x27;Py reads as a variable, a text tag opened and never closed, define used where default belongs, a persistent value with no default, tab indentation, and shipped developer settings. Fourteen rules, each with the fix.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Solo and small-team Ren&#x27;Py developers shipping visual novels to Steam, itch.io and Android. It is aimed at the moment before you press build: the point where your own save file hides bugs that a first-time player will hit on line one of chapter three.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is a chatbot not enough?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A chatbot only sees the snippet you paste. It cannot tell you that persistent.seen_ending_a is read in script.rpy and defaulted in none of your other files, or that config.save_directory is missing from the project. Those two findings need every file at once, and they are the two that lose player saves.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free: open a .rpy file, run the check, get every finding in that file with line numbers and fixes. That is a finished job for a single file, forever, with no key. The licence adds the other axis: one sweep across every .rpy file in the workspace and an exported release report you keep.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost without the tool?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Putting one title on Steam costs $100 in Steam Direct fees before you see a crash report. After launch, each fix is a rebuild, a store update and the players who already refunded. The licence is $29 once, one key per person or CI seat, with a seven-day full refund.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Ren'Py Release Gate actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads your .rpy files and reports the lines that pass Ren'Py's compile step but fail later: a bare square bracket that Ren'Py reads as a variable, a text tag opened and never closed, define used where default belongs, a persistent value with no default, tab indentation, and shipped developer settings. Fourteen rules, each with the fix."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "Solo and small-team Ren'Py developers shipping visual novels to Steam, itch.io and Android. It is aimed at the moment before you press build: the point where your own save file hides bugs that a first-time player will hit on line one of chapter three."}}, {"@type": "Question", "name": "Why is a chatbot not enough?", "acceptedAnswer": {"@type": "Answer", "text": "A chatbot only sees the snippet you paste. It cannot tell you that persistent.seen_ending_a is read in script.rpy and defaulted in none of your other files, or that config.save_directory is missing from the project. Those two findings need every file at once, and they are the two that lose player saves."}}, {"@type": "Question", "name": "What is free and what needs a licence?", "acceptedAnswer": {"@type": "Answer", "text": "Free: open a .rpy file, run the check, get every finding in that file with line numbers and fixes. That is a finished job for a single file, forever, with no key. The licence adds the other axis: one sweep across every .rpy file in the workspace and an exported release report you keep."}}, {"@type": "Question", "name": "What would this cost without the tool?", "acceptedAnswer": {"@type": "Answer", "text": "Putting one title on Steam costs $100 in Steam Direct fees before you see a crash report. After launch, each fix is a rebuild, a store update and the players who already refunded. The licence is $29 once, one key per person or CI seat, with a seven-day full refund."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:0,slug:"renpy-release-gate",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence, 7-day refund.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/firmware-release-gate"><span class="yj-rel-t">Firmware Release Gate for sdkconfig and prj.conf</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/pyproject-release-gate"><span class="yj-rel-t">pyproject.toml Release Gate (PEP 639)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/dotnet-eol-support-gate"><span class="yj-rel-t">.NET EOL &amp; CRA Support Window Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/coverage-gate-lint"><span class="yj-rel-t">Coverage Gate Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/en301549-lint"><span class="yj-rel-t">EN 301 549 Lint — European Accessibility Act rules, with the clause number, in</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/play-release-blocker-lint"><span class="yj-rel-t">Google Play Release Blocker Lint - target API 36, Billing 8, 16 KB</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section><!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div>
352
+
353
+
354
+ <script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
355
+
356
+ <script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script>
357
+ </body>
358
  </html>