Skip to content

Commit 47a0361

Browse files
author
web3blind
committed
Add balanced temple offerings
1 parent 11e9b46 commit 47a0361

17 files changed

Lines changed: 430 additions & 25 deletions

File tree

app/assets/deities/god-labor.svg

Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 8 additions & 0 deletions
Loading

app/css/main.css

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,3 +1701,79 @@ body,
17011701
font-size: 0.82rem;
17021702
line-height: 1.4;
17031703
}
1704+
1705+
1706+
/* Temple */
1707+
.temple-screen {
1708+
max-width: 920px;
1709+
margin: 0 auto;
1710+
}
1711+
1712+
.temple-balance-note,
1713+
.temple-rules {
1714+
color: var(--color-text-muted);
1715+
}
1716+
1717+
.temple-deities {
1718+
display: grid;
1719+
grid-template-columns: repeat(2, minmax(0, 1fr));
1720+
gap: var(--space-md);
1721+
margin: var(--space-md) 0;
1722+
}
1723+
1724+
.temple-card {
1725+
display: grid;
1726+
grid-template-columns: 120px minmax(0, 1fr);
1727+
gap: var(--space-md);
1728+
padding: var(--space-md);
1729+
border: 1px solid var(--color-border);
1730+
border-radius: var(--radius);
1731+
background: var(--color-surface);
1732+
}
1733+
1734+
.temple-card-fire_goddess {
1735+
border-left: 4px solid #ff7a1a;
1736+
}
1737+
1738+
.temple-card-labor_god {
1739+
border-left: 4px solid #58a6ff;
1740+
}
1741+
1742+
.temple-deity-image {
1743+
width: 120px;
1744+
height: 120px;
1745+
border-radius: 24px;
1746+
background: #08080f;
1747+
}
1748+
1749+
.temple-deity-copy h2 {
1750+
margin-bottom: var(--space-xs);
1751+
}
1752+
1753+
.temple-domain,
1754+
.temple-target,
1755+
.temple-reward,
1756+
.temple-cooldown {
1757+
color: var(--color-text-muted);
1758+
font-size: 0.9rem;
1759+
}
1760+
1761+
.temple-rules ul {
1762+
margin-left: 1.2rem;
1763+
}
1764+
1765+
@media (max-width: 720px) {
1766+
.temple-deities {
1767+
grid-template-columns: 1fr;
1768+
}
1769+
}
1770+
1771+
@media (max-width: 480px) {
1772+
.temple-card {
1773+
grid-template-columns: 1fr;
1774+
}
1775+
.temple-deity-image {
1776+
width: 96px;
1777+
height: 96px;
1778+
}
1779+
}

app/index.html

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<link rel="icon" type="image/png" sizes="192x192" href="assets/icons/viz-magic-192.png?v=20260712c">
1414
<link rel="icon" type="image/png" sizes="512x512" href="assets/icons/viz-magic-512.png?v=20260712c">
1515
<link rel="apple-touch-icon" href="assets/icons/viz-magic-192.png?v=20260712c">
16-
<link rel="stylesheet" href="css/main.css?v=20260712f">
16+
<link rel="stylesheet" href="css/main.css?v=20260712g">
1717
<link rel="stylesheet" href="css/themes.css">
1818
<link rel="stylesheet" href="css/accessibility.css">
1919
</head>
@@ -99,6 +99,10 @@
9999
<!-- Screen: Leaderboard -->
100100
<section id="screen-leaderboard" class="screen" role="main" aria-hidden="true" aria-label="Leaderboard">
101101
</section>
102+
103+
<!-- Screen: Temple -->
104+
<section id="screen-temple" class="screen" role="main" aria-hidden="true" aria-label="Temple">
105+
</section>
102106
</div>
103107

104108
<!-- Bottom navigation -->
@@ -113,11 +117,11 @@
113117
<script src="lib/viz.min.js"></script>
114118

115119
<!-- Config -->
116-
<script src="js/config.js?v=20260621m"></script>
120+
<script src="js/config.js?v=20260712a"></script>
117121

118122
<!-- I18n -->
119-
<script src="js/i18n/ru.js?v=20260712f"></script>
120-
<script src="js/i18n/en.js?v=20260712f"></script>
123+
<script src="js/i18n/ru.js?v=20260712g"></script>
124+
<script src="js/i18n/en.js?v=20260712g"></script>
121125

122126
<!-- Utilities -->
123127
<script src="js/utils/helpers.js?v=20260621l"></script>
@@ -128,7 +132,7 @@
128132
<script src="js/blockchain/connection.js"></script>
129133
<script src="js/blockchain/account.js"></script>
130134
<script src="js/blockchain/history-source.js?v=20260621d"></script>
131-
<script src="js/blockchain/broadcast.js?v=20260621m"></script>
135+
<script src="js/blockchain/broadcast.js?v=20260712a"></script>
132136
<script src="js/blockchain/invite.js"></script>
133137

134138
<!-- Protocols -->
@@ -149,7 +153,7 @@
149153
<script src="js/engine/types.js"></script>
150154
<script src="js/engine/formulas.js"></script>
151155
<script src="js/engine/character.js"></script>
152-
<script src="js/engine/items.js"></script>
156+
<script src="js/engine/items.js?v=20260712a"></script>
153157
<script src="js/engine/combat.js?v=20260712c"></script>
154158
<script src="js/engine/duel.js"></script>
155159
<script src="js/engine/duel-state.js"></script>
@@ -161,12 +165,12 @@
161165
<script src="js/engine/marketplace.js"></script>
162166
<script src="js/engine/checkpoint.js"></script>
163167
<script src="js/engine/block-processor.js"></script>
164-
<script src="js/engine/world-events.js?v=20260712f"></script>
168+
<script src="js/engine/world-events.js?v=20260712g"></script>
165169
<script src="js/engine/world-boss.js"></script>
166170
<script src="js/engine/quest-system.js?v=20260621e"></script>
167171
<script src="js/engine/loci.js"></script>
168172
<script src="js/engine/npc-framework.js"></script>
169-
<script src="js/engine/state-engine.js?v=20260621m"></script>
173+
<script src="js/engine/state-engine.js?v=20260712a"></script>
170174
<script src="js/engine/daily-leaderboard-storage.js"></script>
171175
<script src="js/engine/daily-leaderboard.js"></script>
172176

@@ -175,14 +179,14 @@
175179
<script src="js/ui/components/progress-bar.js?v=20260712b"></script>
176180
<script src="js/ui/components/toast.js?v=20260711b"></script>
177181
<script src="js/ui/components/modal.js"></script>
178-
<script src="js/ui/components/nav.js"></script>
182+
<script src="js/ui/components/nav.js?v=20260712a"></script>
179183
<script src="js/ui/components/battle-narrator.js?v=20260712b"></script>
180184

181185
<!-- UI Screens -->
182186
<script src="js/ui/screens/landing.js"></script>
183187
<script src="js/ui/screens/login.js?v=20260711a"></script>
184188
<script src="js/ui/screens/onboarding.js"></script>
185-
<script src="js/ui/screens/home.js?v=20260712f"></script>
189+
<script src="js/ui/screens/home.js?v=20260712g"></script>
186190
<script src="js/ui/screens/character.js?v=20260621l"></script>
187191
<script src="js/ui/screens/hunt.js"></script>
188192
<script src="js/ui/screens/inventory.js?v=20260621l"></script>
@@ -196,10 +200,11 @@
196200
<script src="js/ui/screens/quests.js?v=20260621n"></script>
197201
<script src="js/ui/screens/world-boss.js"></script>
198202
<script src="js/ui/screens/settings.js?v=20260712b"></script>
199-
<script src="js/ui/screens/help.js?v=20260711d"></script>
203+
<script src="js/ui/screens/help.js?v=20260712a"></script>
200204
<script src="js/ui/screens/leaderboard.js"></script>
205+
<script src="js/ui/screens/temple.js?v=20260712a"></script>
201206

202207
<!-- Main App Controller (must be last) -->
203-
<script src="js/ui/app.js?v=20260711d"></script>
208+
<script src="js/ui/app.js?v=20260712a"></script>
204209
</body>
205210
</html>

app/js/blockchain/broadcast.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,38 @@ var VizBroadcast = (function() {
206206
});
207207
}
208208

209+
210+
/**
211+
* Send a Temple offering: VM proof + award to a symbolic VIZ account.
212+
* The in-game result is deliberately small and cosmetic to avoid pay-to-win balance.
213+
* @param {string} deityId
214+
* @param {string} targetAccount - null or committee
215+
* @param {number} energy - mana basis points
216+
* @param {Function} callback
217+
*/
218+
function templeOffering(deityId, targetAccount, energy, callback) {
219+
var memo = 'viz://vm/temple/' + deityId;
220+
var actionData = {
221+
t: cfg.ACTION_TYPES.TEMPLE_OFFERING,
222+
d: { deity: deityId, target: targetAccount, energy: energy }
223+
};
224+
225+
gameAction(actionData, function(err, result) {
226+
if (err) {
227+
callback(err);
228+
return;
229+
}
230+
award(targetAccount, energy, 0, memo, [], function(awardErr, awardResult) {
231+
if (awardErr) {
232+
console.log('Temple award failed (VM offering proof was recorded):', awardErr);
233+
callback(awardErr);
234+
return;
235+
}
236+
callback(null, { action: result, award: awardResult || null });
237+
});
238+
});
239+
}
240+
209241
/**
210242
* Broadcast quest action (accept / complete / abandon) as a VM custom op.
211243
* @param {string} type
@@ -307,6 +339,7 @@ var VizBroadcast = (function() {
307339
gameAction: gameAction,
308340
huntAction: huntAction,
309341
armageddonAction: armageddonAction,
342+
templeOffering: templeOffering,
310343
questAction: questAction,
311344
updateMetadata: updateMetadata,
312345
chroniclePost: chroniclePost,

app/js/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ var VizMagicConfig = (function() {
183183
QUEST_COMPLETE: 'quest.complete',
184184
QUEST_ABANDON: 'quest.abandon',
185185
BOSS_ATTACK: 'boss.attack',
186+
TEMPLE_OFFERING:'temple.offering',
186187
// Guild discovery
187188
GUILD_LISTING: 'guild.listing'
188189
};

app/js/engine/items.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ var ItemSystem = (function() {
145145
category: CATEGORIES.RELIC,
146146
slot: SLOTS.ACCESSORY,
147147
baseStats: {}
148+
},
149+
flame_votive_mark: {
150+
category: CATEGORIES.RELIC,
151+
slot: null,
152+
baseStats: {}
153+
},
154+
labor_votive_mark: {
155+
category: CATEGORIES.RELIC,
156+
slot: null,
157+
baseStats: {}
148158
}
149159
};
150160

app/js/engine/state-engine.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ var StateEngine = (function() {
265265
case AT.HUNT_ARMAGEDDON:
266266
events = events.concat(_handleHuntArmageddon(sender, action.data, blockNum));
267267
break;
268+
case AT.TEMPLE_OFFERING:
269+
events = events.concat(_handleTempleOffering(sender, action.data, blockNum));
270+
break;
268271
case AT.ITEM_EQUIP:
269272
events = events.concat(_handleEquip(sender, action.data, blockNum));
270273
break;
@@ -549,6 +552,48 @@ var StateEngine = (function() {
549552
}];
550553
}
551554

555+
556+
/**
557+
* Handle a Temple offering. Rewards are intentionally non-combat cosmetic relics.
558+
* Cooldown prevents spam and keeps the economy from becoming pay-to-win.
559+
*/
560+
function _handleTempleOffering(sender, data, blockNum) {
561+
if (!worldState.characters[sender]) return [];
562+
var deity = data.deity || '';
563+
var target = data.target || '';
564+
var energy = Number(data.energy || 0);
565+
var cfgByDeity = {
566+
fire_goddess: { target: 'null', minEnergy: 50, item: 'flame_votive_mark' },
567+
labor_god: { target: 'committee', minEnergy: 50, item: 'labor_votive_mark' }
568+
};
569+
var def = cfgByDeity[deity];
570+
if (!def || target !== def.target || energy < def.minEnergy) return [];
571+
572+
if (!worldState.temple) worldState.temple = {};
573+
if (!worldState.temple[sender]) worldState.temple[sender] = {};
574+
var lastBlock = worldState.temple[sender][deity] || 0;
575+
var cooldown = 28800; // roughly one day
576+
if (lastBlock && blockNum && blockNum - lastBlock < cooldown) {
577+
return [{ type: 'temple_offering_rejected', account: sender, deity: deity, reason: 'cooldown' }];
578+
}
579+
580+
worldState.temple[sender][deity] = blockNum || 0;
581+
if (!worldState.inventories[sender]) worldState.inventories[sender] = [];
582+
var item = ItemSystem.createItem(def.item, sender, 1, blockNum || 0, sender, false);
583+
item.templeDeity = deity;
584+
item.nonCombat = true;
585+
worldState.inventories[sender].push(item);
586+
return [{
587+
type: 'temple_offering',
588+
account: sender,
589+
deity: deity,
590+
target: target,
591+
energy: energy,
592+
itemType: def.item,
593+
itemId: item.id
594+
}];
595+
}
596+
552597
/**
553598
* Handle item equip
554599
*/
@@ -1274,6 +1319,20 @@ var StateEngine = (function() {
12741319
return result;
12751320
}
12761321

1322+
1323+
/**
1324+
* Process Temple offering for live UI — same logic as replay.
1325+
*/
1326+
function processTempleOfferingResult(account, deityId, targetAccount, energy, blockNum) {
1327+
var events = _handleTempleOffering(account, {
1328+
deity: deityId,
1329+
target: targetAccount,
1330+
energy: energy
1331+
}, blockNum || 0);
1332+
if (!events.length) return null;
1333+
return events[0];
1334+
}
1335+
12771336
/**
12781337
* Process movement for live UI — same authoritative mutation path as replay.
12791338
* @param {string} account
@@ -1395,6 +1454,7 @@ var StateEngine = (function() {
13951454
processQuestCompleteResult: processQuestCompleteResult,
13961455
processQuestAbandonResult: processQuestAbandonResult,
13971456
processArmageddonResult: processArmageddonResult,
1457+
processTempleOfferingResult: processTempleOfferingResult,
13981458
reset: reset
13991459
};
14001460
})();

app/js/i18n/en.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,33 @@ var LangEN = {
10091009
settings_realm_archives: 'Realm Archives',
10101010

10111011
// Help screen
1012+
nav_temple: 'Temple',
1013+
temple_title: 'Temple',
1014+
temple_intro: 'Mages make small offerings to the gods of the world here. An offering spends mana through a VIZ award and leaves a modest sign of faith in the game.',
1015+
temple_balance_note: 'Balance: this is not buying power. For now the Temple grants lore relics without combat stats, with a daily limit per deity.',
1016+
temple_rules_title: 'Offering rules',
1017+
temple_rule_cost: 'One offering costs 0.50% mana.',
1018+
temple_rule_cooldown: 'A deity cannot be spammed: roughly one offering per in-world day.',
1019+
temple_rule_no_pay_to_win: 'The reward does not directly boost combat, so the economy stays safe and the Temple does not become pay-to-win.',
1020+
temple_fire_goddess_name: 'Goddess of Fire',
1021+
temple_fire_goddess_domain: 'Flame, cleansing, burnt pride',
1022+
temple_fire_goddess_text: 'The offering goes to null — sacrificial fire where VIZ burns like smoke above the altar.',
1023+
temple_labor_god_name: 'God of Labor',
1024+
temple_labor_god_domain: 'Hammer, craft, common work',
1025+
temple_labor_god_text: 'The offering goes to the VIZ development committee: not a private idol, but the building of the world.',
1026+
temple_offering_target: 'Offering account',
1027+
temple_reward: 'Sign',
1028+
temple_offer_button: 'Offer {cost}',
1029+
temple_offering_started: 'Sending the offering to the chain...',
1030+
temple_offering_failed: 'Offering failed. Mana should not count as spent in game.',
1031+
temple_offering_success: 'Offering accepted. A sign of faith was added to your bag.',
1032+
temple_cooldown_active: 'This deity has already accepted an offering today.',
1033+
temple_last_offering_recorded: 'A recent offering is already recorded in the world.',
1034+
temple_no_offering_yet: 'No offering to this deity has been recorded yet.',
1035+
item_flame_votive_mark: 'Altar Spark',
1036+
item_labor_votive_mark: 'Labor Seal',
1037+
help_section_temple: 'Temple',
1038+
help_temple_text: 'At the Temple, you can make small offerings: to the Goddess of Fire through null and to the God of Labor through committee. Rewards are cosmetic/lore-first for balance.',
10121039
nav_help: 'Guide',
10131040
help_title: 'Game Guide',
10141041
help_intro: 'Viz Magic is a browser RPG on the VIZ blockchain. Every action — hunt, craft, trade — is permanently recorded on-chain.',

0 commit comments

Comments
 (0)